* Writing my first plugin

For users to report plugin bugs and request plugin enhancements; and for authors to test new/new versions of plugins, and to discuss plugin development (in the Programming Technicalities sub-forum). If you want advice on choosing or using a plugin, please ask in General Usage or an appropriate sub-forum.
Post Reply
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Writing my first plugin

Post by laz_gen »

I would welcome a little guidance with my first plugin. I am trying to move & copy some data within a particular fact. The move I have achieved but the copy bit I am finding a little harder.

My starting point is the code snippet AllItems.fh_Lua

The Fact in question is Marriage Witness and I wanted to move the text from the note field to the main field. This I have achieved OK using this code.

Code: Select all

for ptr in allItems('_ATTR-MARRIAGE_WITNESS.NOTE2','FAM') do
    
	strVal=fhGetItemText(ptr,'_ATTR-MARRIAGE_WITNESS.NOTE2')

	if strVal ~= '' then
	    thisptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS')
	    fhSetValueAsText(thisptr,strVal) 

	   thisptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS.NOTE2')
	   fhSetValueAsText(thisptr,'')
	end

end
Th other thing I would like to do is copy the Marriage Date (%FAM.MARR.DATE%) to (%FAM._ATTR-MARRIAGE_WITNESS.DATE%)

I am guessing dates are handled a little different to text fields so I am struggling.

All help appreciated.
User avatar
Jane
Site Admin
Posts: 8514
Joined: 01 Nov 2002 15:00
Family Historian: V7
Location: Somerset, England
Contact:

Re: Writing my first plugin

Post by Jane »

Assuming you want to learn I'll just give you some hints for now.

A few things leap out, your original code does not handle if the field you are moving to does not exist so you need to test for thisptr being null and then use fhCreateItem to add one.

To move dates around you need to use a Date Object and fhGetValueAsDate.
Jane
My Family History : My Photography "Knowledge is knowing that a tomato is a fruit. Wisdom is not putting it in a fruit salad."
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

I took a little break and realised my first part that was doing the move was doing so within a fact, from one field to another. That bit I managed OK, and have just realised the second part will be copying a field from one fact to another, so slightly different.

I was trying to use the same code snippet AllItems.fh_Lua and am now wondering if it might not work copying between facts.

This is what I have so far. The code completes without error but doesn't make the copy. I think I may be getting my pointers mixed up!

Code: Select all

for ptr in allItems('_ATTR-MARRIAGE_WITNESS','FAM') do
    
	dtptr = fhGetItemPtr(ptr,'FAM.MARR.DATE')
        strDat = fhGetValueAsDate(dtptr)
 	if strDat ~= '' then
		thisptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS.DATE')
		fhSetValueAsDate(thisptr, strDat) 
	end

end
User avatar
Jane
Site Admin
Posts: 8514
Joined: 01 Nov 2002 15:00
Family Historian: V7
Location: Somerset, England
Contact:

Re: Writing my first plugin

Post by Jane »

You are not testing for the existence of the pointer when you issue

thisptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS.DATE')

If there is no _ATTR-MARRIAGE_WITNESS.DATE field already in existence it will not return a pointer.

You need to check there is a FAM.MARR.DATE exists for the Family

To check if there is a date field to update you need to check you got a dtptr back and that strDat:IsNotNull()

It's best to use a table to build a list of all the items you want to add when looping through and then apply them at the end or the loop through can "get lost". It's normally find for copy and add but deletes can cause problems.
Jane
My Family History : My Photography "Knowledge is knowing that a tomato is a fruit. Wisdom is not putting it in a fruit salad."
User avatar
tatewise
Megastar
Posts: 28414
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Writing my first plugin

Post by tatewise »

May I briefly interject with a couple of observations.

Firstly, the allItems(...) function only accepts Record types such as INDI, FAM, SOUR, OBJE, etc.
i.e. Those that can appear at the root of a Data Reference and have a tab in the Records Window.
_ATTR-MARRIAGE_WITNESS is not a Record Type and the defensive checks within the function just ignore it.
It is a subsidiary field within Family (FAM) records with the special type of Fact and sub-type Attribute.

Secondly, in your original posting, it is setting the Note field to an empty text string rather than delete it.
FH will automatically clean up and delete that empty Note field eventually.
The Plugin could use fhDeleteItem(ptr) but take note of Jane's advice about delaying those operations.
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

To save confusing myself I have made this into two separate little plugins. I have saved the first one that does the moving of the text fields. This second one for copying the dates will be completely separate.

Am I right in thinking blank fields such as these marriage witness dates don't exist empty and are only created when data is saved to them from the user interface. So from a programming point of view do I need to detect if it is first present, then if not, create it then do the copy.

This is what I have. It completes without error but does not copy the dates. (mptr is the pointer to the marriage date, mDat is the marriage date value, and wptr is the witness date pointer)

Code: Select all

for ptr in allItems('_ATTR-MARRIAGE_WITNESS','FAM') do
    
	mptr = fhGetItemPtr(ptr,'FAM.MARR.DATE')

	if mptr:IsNotNull() then
   		mDat = fhGetValueAsDate(mptr)

		wptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS.DATE')

		if wptr:IsNull() then
			wptr = fhCreateItem('_ATTR-MARRIAGE_WITNESS.DATE', ptr )
		else
			wptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS.DATE')
			fhSetValueAsDate(wptr,mDat) 
		end
	end

end
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

Mike

Just seen your reply and note the comment about not storing an empty text string. I will look at using fhDeleteItem(ptr) to delete the field.

As for my attempt at copying these dates it seems my general approach will not work using AllItems. I will have to think again.
User avatar
tatewise
Megastar
Posts: 28414
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Writing my first plugin

Post by tatewise »

Yes, it will work using allItems(...) but did you see my point about its parameters?

Have you tried stepping through your Plugin in the Editor/Debug window?

As it stands it will only set the Date value if _ATTR-MARRIAGE_WITNESS.DATE already exists, which is unlikely.

If it IsNull() then you are creating it, but then don't set its value.
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

Yes, I was concentrating so much on detecting and creating the object I forgot to set its value. However It still doesn't work.

I have altered the parameter sent to AllItems to be ('MARR','FAM'). Not sure if this is appropriate.

I have tried to follow the progress using the debugger. I can see ptr pointing to a marriage record, then mptr points to a marriage date and mDat is set with the date string. So far, so good.

It doesn't seem to be setting the date value and bOK always returns false.

Code: Select all

for ptr in allItems('MARR','FAM') do
    
	mptr = fhGetItemPtr(ptr,'FAM.MARR.DATE')

	if mptr:IsNotNull() then
   		mDat = fhGetValueAsDate(mptr)

		wptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS.DATE')

		if wptr:IsNull() then
			ptrFam = fhCreateItem("FAM")
			wptr = fhCreateItem('_ATTR-MARRIAGE_WITNESS.DATE', ptrFam )
			bOK = fhSetValueAsDate(wptr,mDat)
		else
			wptr = fhGetItemPtr(ptrFam,'_ATTR-MARRIAGE_WITNESS.DATE')
			bOK = fhSetValueAsDate(wptr,mDat) 
		end
	end

end
User avatar
tatewise
Megastar
Posts: 28414
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Writing my first plugin

Post by tatewise »

OK, you are digging deeper into trouble due to your understandable inexperience.

Firstly, in allItems('MARR','FAM') the MARR tag is invalid, as it is NOT a record type.
Secondly, that is an inappropriate looping iterator.
See plugins:code_snippets:loop_through_records|> Loop Through Records (code snippet) and its Using an Iterator Function.
Use iterator function records('FAM') instead, so ptr refers to each Family record in turn.

ptrFam = fhCreateItem("FAM") creates a brand NEW empty Family record, which you do NOT need.

The very first thing to do is discover if the Family record has a Marriage Witness attribute.
Only if that attribute fact exists, does the Marriage Date need copying.
local factptr = fhGetItemPtr(ptr,'_ATTR-MARRIAGE_WITNESS')
if factptr:IsNotNull() then

You also need to ensure that fact attribute has a Date field.
local dateptr = fhGetItemPtr(factptr,'_ATTR-MARRIAGE_WITNESS.DATE')
if dateptr:IsNull() then
dateptr = fhCreateItem('DATE', factptr )
end


Now you can obtain the Marriage Date value and set it into the Marriage Witness Date.

BTW: Notice the use of local variables which is a good habit to get into for later more complex Plugins.
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

Success!

Strangely it didn't work with
local dateptr = fhGetItemPtr(factptr,'_ATTR-MARRIAGE_WITNESS.DATE') until I changed it to
local dateptr = fhGetItemPtr(factptr,'FAM._ATTR-MARRIAGE_WITNESS.DATE') adding the "FAM." to make the full fact/attribute name.

This was my final code:

Code: Select all

function records(type)
    local pi = fhNewItemPtr()
    local p2 = fhNewItemPtr()
    pi:MoveToFirstRecord(type)
    return function ()
        p2:MoveTo(pi)
        pi:MoveNext()
        if p2:IsNotNull() then return p2 end
    end
end


for ptr in records('FAM') do

	local factptr = fhGetItemPtr(ptr,'FAM._ATTR-MARRIAGE_WITNESS')

	if factptr:IsNotNull() then
    
		local dateptr = fhGetItemPtr(factptr,'FAM._ATTR-MARRIAGE_WITNESS.DATE')
			
		if dateptr:IsNull() then
			dateptr = fhCreateItem('DATE', factptr )
		end
		
		local mptr = fhGetItemPtr(ptr,'FAM.MARR.DATE')

		if mptr:IsNotNull() then
   			local mDat = fhGetValueAsDate(mptr)
			bOK = fhSetValueAsDate(dateptr,mDat)
		end

	end

end
Thanks for all your help.
User avatar
tatewise
Megastar
Posts: 28414
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Writing my first plugin

Post by tatewise »

Can you double-check your script, because I'm 99.9% certain that it cannot work.

local dateptr = fhGetItemPtr(factptr,'FAM._ATTR-MARRIAGE_WITNESS.DATE') just does not work.

local dateptr = fhGetItemPtr(ptr,'FAM._ATTR-MARRIAGE_WITNESS.DATE') will work.

local dateptr = fhGetItemPtr(factptr,'_ATTR-MARRIAGE_WITNESS.DATE') will work.

local dateptr = fhGetItemPtr(factptr,'~.DATE') will work, which uses a new shortcut concept.

BTW: There is a neater way to copy a field:
local bOK = fhSetValue_Copy(dateptr,mptr)
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

I can assure you the script works as shown. It was a direct copy and paste from the plugin editor.

I previously created a query to find the marriage records in question with the result set showing the relevant fields. Each time I run the plugin I had the editor sat above the result set and saw the date column copy over as intended.

I have just tried the plugin again with a fresh gedcom export from Legacy. It still works as shown and will only work if I use the "FAM." prefix in the line

local factptr = fhGetItemPtr(ptr,'FAM._ATTR-MARRIAGE_WITNESS')

and it will work with or without the prefix in the line

local dateptr = fhGetItemPtr(factptr,'_ATTR-MARRIAGE_WITNESS.DATE')

Thanks for the alternative copy method. I will add that to my collection of code snippets.
User avatar
tatewise
Megastar
Posts: 28414
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Writing my first plugin

Post by tatewise »

Sorry, but look again. The full Plugin script as posted earlier contains:
local dateptr = fhGetItemPtr(factptr,'FAM._ATTR-MARRIAGE_WITNESS.DATE') that won't work.
You cannot use the factptr with a FAM rooted data reference.

I don't doubt the Plugin works, but NOT with the code shown earlier.
Try copy & pasting it back and it won't work.

You have just posted two lines that work:

local factptr = fhGetItemPtr(ptr,'FAM._ATTR-MARRIAGE_WITNESS') this gets the factptr and needs FAM.
It works because the ptr refers to a FAM record.

local dateptr = fhGetItemPtr(factptr,'_ATTR-MARRIAGE_WITNESS.DATE') this gets dateptr and must NOT use FAM.
It works because the factptr refers to _ATTR-MARRIAGE_WITNESS fact.

You have to look closely at the reference pointer and the associated data reference.
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

I have posted several scripts through the day but I presume you are talking about the one I posted at 19:57 that I labelled success.

As you suggested I have just copied and pasted it back into a fresh empty editor screen, saved and run it.

It works perfectly copying 3000+ dates from the marriage date column to the marriage witness date column.

Perhaps I should figure a way to export a subset of my gedcom and email to you so you can try it yourself.
User avatar
tatewise
Megastar
Posts: 28414
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Writing my first plugin

Post by tatewise »

You are correct it does work, but only by luck in some circumstances.

I bet none of your Marriage Witness attributes have a Date initially.

If you add a Date to one or two of them you will find they won't get updated.
Even if you delete the Date values they still won't get updated.

local dateptr = fhGetItemPtr(factptr,'FAM._ATTR-MARRIAGE_WITNESS.DATE') ALWAYS gives a NULL pointer as it's invalid.
You could delete that line, and the if dateptr:IsNull() then and the end and the Plugin would still work as long as your Marriage Witness facts have no Date values.

local dateptr = fhGetItemPtr(factptr,'ATTR-MARRIAGE_WITNESS.DATE') is what works
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry
User avatar
laz_gen
Famous
Posts: 177
Joined: 03 Apr 2018 14:02
Family Historian: V7
Contact:

Re: Writing my first plugin

Post by laz_gen »

I am not new to programming but I am new to Lua and the FH API. I don't have that detailed knowledge of the data structures within FH to comment further so am just pleased from a users point of view that my dates were moved as intended.

My thanks to Jane & Mike for guiding me to a solution. :D
Post Reply