Module:CineMol

local p = {}


local compress = require( 'Module:libDeflate' )
require( 'strict' )
local m = require( 'Module:CineMol/api' )
local parse_sdf = require( 'Module:CineMol/parsers' ).parse_sdf
local getArgs = require( 'Module:Arguments' ).getArgs
local yesno = require( 'Module:Yesno' )

-- Stolen from Module:Cite_taxon
local b64decode = function(data)
    local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    data = string.gsub(data, '[^'..b..'=]', '') 
    return (data:gsub('.', function(x)
        if (x == '=') then return '' end 
        local r,f='',(b:find(x)-1)
        for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end 
        return r;
    end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
        if (#x ~= 8) then return '' end 
        local c=0 
        for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end 
        return string.char(c)
    end))
end

-- Keep in sync with Category:Tabular_data_of_the_Chemical_Component_Dictionary
local function getKey( id )
	if string.sub( id, 1, 1 ) == 'A' then
		local letter2 = #id <= 1 and 0 or string.byte( id, 2 )
		local letter3 = #id <= 2 and 0 or string.byte( id, 3 )
		if
			letter2 < string.byte( '1' ) or
			( string.sub( id, 1, 2 ) == 'A1' and letter3 < string.byte( 'B' ) )
		then
			return 'A'
		elseif string.sub( id, 1, 2 ) == 'A1' and letter3 < string.byte( 'I' ) then
			return 'A1B';
		elseif string.sub( id, 1, 2 ) == 'A1' then
			return 'A1I'
		else
			return 'A2'
		end
	else
		return string.sub( id, 1, 1)
	end
end

local function is2D( mol )
	return string.match( mol, "^[^\n]*\n[^\n]*(2D)[\n ]" ) == '2D'
end

local function getMolFromCCD( id )
	local data = mw.ext.data.get( string.format( "Chemical Component Dictionary/%s.tab", getKey(id) ) )
	assert( data ~= false, 'Could not fetch CCD file from commons')
	local ccd = data['data']
	local molEncoded = ''
	for i = 1,#ccd do
		-- This assumes entries are in order.
		-- if this becomes an efficiency issue, we could binary search it instead.
		if ccd[i][1] == id then
			molEncoded = molEncoded .. ccd[i][3]
		end
	end
	assert( molEncoded ~= '', 'Could not find PDB-lignad id: ' .. id )
	return compress:DecompressDeflate( b64decode( molEncoded ) )
end

local function getMolFromChEBI( id )
	if string.upper(string.sub( id, 1, 6 )) == 'CHEBI:' then
		id = string.sub( id, 7 )
	end
	local idNumb = tonumber( id )
	local key
	if idNumb < 160000 then
		key = math.floor( idNumb / 2000 ) * 2000
	elseif idNumb < 200000 then
		key = math.floor( idNumb / 20000 ) * 20000
	else
		key = 200000
	end
	local data = mw.ext.data.get( string.format( "ChEBI/%d.tab", key ) )
	assert( data ~= false, 'Could not fetch ChEBI data file from commons')
	local dataset = data['data']
	local molEncoded = ''
	for i = 1,#dataset do
		-- This assumes entries are in order.
		-- if this becomes an efficiency issue, we could binary search it instead.
		if dataset[i][1] == id then
			molEncoded = molEncoded .. dataset[i][4]
		end
	end
	assert( molEncoded ~= '', 'Could not find ChEBI: ' .. id )
	return compress:DecompressDeflate( b64decode( molEncoded ) )
end

p.styleMap = {
	spacefilling = m.Style.SPACEFILLING,
	space_filling = m.Style.SPACEFILLING,
	["space filling"] = m.Style.SPACEFILLING,
	ballandstick = m.Style.BALL_AND_STICK,
	ball_and_stick = m.Style.BALL_AND_STICK,
	["ball and stick"] = m.Style.BALL_AND_STICK,
	tube = m.Style.TUBE,
	wireframe = m.Style.WIREFRAME
}

p.lookMap = {
	cartoon = m.Look.CARTOON,
	glossy = m.Look.GLOSSY
}

function p.draw_mol(frame)
	local args = getArgs( frame )
	return frame:extensionTag( "TemplateStyles", "", {src="Module:CineMol/styles.css"} )
		.. p.draw_mol_internal(args)
end

-- Get automatic caption if using a ligand id
local function getAutoCaptionPdb(ligand)
	local license = '<div class="cinemol-license-img" title="Public domain">[[File:Cc.logo.circle.svg|28px|class=skin-invert notpageimage|link=|Creative Commons]]&nbsp;[[File:Cc-zero.svg|28px|class=skin-invert notpageimage|link=https://creativecommons.org/publicdomain/zero/1.0/deed.en|CC-Zero]]</div>'
	return license .. 'Rendered based on the entry in the [https://www.wwpdb.org/data/ccd Chemical Component Dictionary] for the molecule with id "[https://www.rcsb.org/ligand/' .. ligand .. ' ' .. ligand .. ']".'
end

local function getAutoCaptionChEBI(id)
	local license = '<div class="cinemol-license-img" title="Creative Commons Attribution 4.0">[[File:Cc.logo.circle.svg|28px|class=skin-invert notpageimage|link=https://creativecommons.org/licenses/by/4.0/deed.en|Creative Commons]]&nbsp;[[File:Cc-by_new_white.svg|28px|class=skin-invert notpageimage|link=https://creativecommons.org/licenses/by/4.0/deed.en|CC-BY 4.0]]</div>'
	return license .. 'Rendered based on the entry in the [[w:ChEBI|Chemical Entities of Biological Interest]] for the molecule with id "[https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:' .. id .. ' CHEBI:' .. id .. ']".'
end

function p.draw_mol_internal(args)
	local style = args.style == nil and m.Style.SPACEFILLING or p.styleMap[string.lower(args.style)]
	assert( style ~= nil, 'Unrecognized style' )
	local look = args.look == nil and m.Look.GLOSSY or p.lookMap[string.lower(args.look)]
	assert( look ~= nil, 'Unrecognized look' )
	local resolution = args.resolution == nil and 30 or tonumber(args.resolution)
	assert( type(resolution) == 'number', 'Resolution must be number' )
	-- We can never zoom in so far that the molecule is out of frame, so best this default
	-- a high number.
	local scale = args.scale == nil and 5 or tonumber(args.scale)
	assert( type(scale) == 'number', 'Scale must be number' )
	local focal_length = args.focal_length ~= nil and tonumber(args.focal_length) or nil
	local rx = args.rx == nil and 0 or tonumber(args.rx)
	local ry = args.ry == nil and 0 or tonumber(args.ry)
	local rz = args.rz == nil and 0 or tonumber(args.rz)
	local useLightbox = yesno( args.lightbox, true ) or true
	local hydrogen = yesno( args.include_hydrogen, false ) or false
	local exclude = hydrogen and {} or {'H'}
	local width = args.width
	if args.height == nil and args.width == nil then
		width = "250"
	end
	local internalLink = nil
	local externalLink = nil
	if args.link ~= nil then
		if string.match( args.link, '^https?://' ) or string.sub( args.link, 1, 2 ) == '//' then
			externalLink = args.link
			useLightbox = false
		else
			internalLink = args.link
			useLightbox = false
		end
	end

	local lightboxCaption = args.lightbox_caption
	local mol
	if args.mol ~= nil and args.mol ~= '' then
		mol = args.mol
	elseif args['pdb-ligand'] ~= nil and args['pdb-ligand'] ~= '' then
		mol = getMolFromCCD( args['pdb-ligand'] )
		lightboxCaption = lightboxCaption == nil and getAutoCaptionPdb( args['pdb-ligand'] ) or lightboxCaption
	elseif args.ChEBI ~= nil and args.ChEBI ~= '' then
		mol = getMolFromChEBI( args.ChEBI )
		lightboxCaption = lightboxCaption == nil and getAutoCaptionChEBI( args.ChEBI ) or lightboxCaption
	else
		local wd = args.wikidata == nil and mw.wikibase.getEntityIdForCurrentPage() or args.wikidata
		if wd ~= nil then
			local stmt = mw.wikibase.getBestStatements( wd, 'P3636' )
			if stmt and stmt[1] and stmt[1].mainsnak then
				local ccd = stmt[1].mainsnak.datavalue.value
				mol = getMolFromCCD( ccd )
				lightboxCaption = lightboxCaption == nil and getAutoCaptionPdb( ccd ) or lightboxCaption
			else
				stmt =  mw.wikibase.getBestStatements( wd, 'P683' )
				if stmt and stmt[1] and stmt[1].mainsnak then
					local ChEBI = stmt[1].mainsnak.datavalue.value
					mol = getMolFromChEBI( ChEBI )
					lightboxCaption = lightboxCaption == nil and getAutoCaptionChEBI( ChEBI ) or lightboxCaption
				end
			end
		end
	end

	assert( mol ~= nil and mol ~= '', 'Either mol, pdf-ligand, ChEBI or wikidata argument is required or the current page has to be connected to a wikidata item with P3636' )

	local style = args.style == nil and ( is2D(mol) and m.Style.WIREFRAME or m.Style.SPACEFILLING ) or p.styleMap[string.lower(args.style)]
	assert( style ~= nil, 'Unrecognized style' )

	-- Actual template
	local atoms, bonds = parse_sdf( mol )
	local molSvg = m.draw_molecule( atoms, bonds, style, look, resolution, nil, nil, rx, ry, rz, scale, focal_length, exclude )

	local mw_svg = molSvg
		:to_mw_svg()
		
	if ( width ) then
		mw_svg:setImgAttribute( 'width', tostring( width ) )
	end
	if ( args.css ) then
		mw_svg:setImgAttribute( 'style', tostring( args.css ) )
	end
	if ( args.alt ) then
		mw_svg:setImgAttribute( 'alt', tostring( args.alt ) )
	end
	if ( args.class ) then
	end
	if ( args.height ) then
		mw_svg:setImgAttribute( 'height', tostring( args.height ) )
	end
	if ( args.title ) then
		mw_svg:setImgAttribute( 'title', tostring( args.title ) )
	end
	local class = 'cinemol-img'
	if args.class then
		class = class .. ' ' .. args.class
	end
	mw_svg:setImgAttribute( 'class', class )

	local start = '<div class="plainlinks cinemol-container calculator-container">'
	local tail = '</div>'
	if useLightbox then
		start = start .. '<div class="calculator-field cinemol-inner-container" data-calculator-type="passthru" id="calculator-field-lightbox">'
		tail = '</div>' .. tail
	end

	if lightboxCaption and useLightbox then
		-- potentially should have an aria-described-by pointing to it.
		tail = '<div class="cinemol-lightbox-caption">' .. lightboxCaption .. '</div>' .. tail
	end
	if useLightbox then
		start = start .. '<div class="cinemol-lightbox-controls">' ..  mw.getCurrentFrame():preprocess('{{calculator button|contents=×|style=appearance:none|alt=Exit molecule lightbox view|title=Exit molecule lightbox view|for=lightbox|formula=0|type=plain}}') .. '</div>'

		-- For now you can only open by click not by tab. It is unclear if there is any value for a screen reader
		-- to zoom in on an image.
		start = start .. '<div class="cinemol-inner calculator-field-buttonraw" data-calculator-for="lightbox" data-calculator-formula="1">'
		tail = '</div>' .. tail
	end
	
	if internalLink then
		start = start .. '[[' .. internalLink .. '|'
		tail =  ']]' .. tail
	elseif externalLink then
		start = start .. '[' .. externalLink .. ' '
		tail =  ']' .. tail
	end

	return start .. mw_svg:toImage() .. tail
end

-- Demo directly using the api
function p.spacefilling(frame)
		local width = frame.args.width == nil and '250' or frame.args.width
        local svg = m.draw_molecule(
        	{
            	m.Atom(0, "C", {0, 0, 0}),
            	m.Atom(1, "H", {1, 0, 0}),
            	m.Atom(2, "H", {0, 1, 0}),
            	m.Atom(3, "H", {0, 0, 1})
        	},
        	{
            	m.Bond(0, 1, 1),
            	m.Bond(0, 2, 1),
            	m.Bond(0, 3, 1),
        	},
            m.Style.SPACEFILLING,
            m.Look.GLOSSY,
            50
        )
		return svg:to_mw_svg():setImgAttribute( 'width', tostring(width) ):toImage()
end

return p

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.