Copy link to clipboard
Copied
Hi there
I'm having an issue with tables using the FrameMaker 8 API.I'm trying to process a document which has about 7 or 8 tables in it. Part of what I do is delete all the strings in a Frame Document and replace them with their equivalents in another language. The deletion is performed by querying the text items in a paragraph, iterating through each of them, finding the FTI_String objects, seleting them, and clearing them. However, I'm finding that the API is also deleting some of the tables in the document.
The code I use to do string deletion is :
F_TextItemsT textItems = F_ApiGetText(m_hDocument, hParagraph, -1);
int nItem = (int)textItems.len;
while (textItems.len > 0 && nItem >= 0)
{
F_TextItemT thisTextItem = textItems.val[nItem];
if (thisTextItem.dataType == FTI_String)
{
StringT string = thisTextItem.u.sdata; //get text item string
CString szText = ConvertFromStringT(string); //convert to CString (for debugger)
F_TextRangeT range;
range.beg.objId = hParagraph;
range.end.objId = hParagraph;
range.beg.offset = thisTextItem.offset; //get start,end offsets of current text item
int nTextLength = szText.GetLength();
range.end.offset = thisTextItem.offset + nTextLength;
F_ApiSetTextRange(FV_SessionId,
m_hDocument,
FP_TextSelection,
&range); // I only want to select strings, but some tables are selected too
F_ApiClear(m_hDocument, 0);
}
}
However, with some (but not all) of the tables, F_ApiSetTextRange is selecting entire tables as well, for reasons I can't understand. and deleting them.
I was wondering if it's a problem in my approach, or if it's a possible problem with the document. I did notice that it was originally authored in FrameMaker 7, but it's being parsed with the FDK8 but I'm assuming that shouldn't make a difference.
Thanks
Eric
Copy link to clipboard
Copied
Hi Eric,
I haven't had do deal with this problem myself, but I have a few ideas.
I can't see any problem in your code that would directly cause this... a table anchor resides somewhere in the previous paragraph, wherever the insertion point was when it was inserted. From a F_TextRange perspective, it occupies a single "space," ie, a single character space. To select it, you would use an offset range of 1, as if selecting a single character.
In your code, when you set the offset for text selection, it seems like it should work. Somehow, though, your code must be adding an additional value to the end offset, causing an anchor to be selected. Or, something like that. Here are two ideas:
- Instead of using the GetLength() method to calculate the end offset, consider using the offset directly from the next text item. For example:
range.end.offset = textItems.val[nItem + 1].offset;
...of course you would need provisions in case you are actually at the last item, otherwise big crashes cometh.
I don't think that would happen in this case, though, because you should always at least have a paragraph mark at the end of the array to handle a string before it.
- Rearchitect your documents so that all tables have a single, empty paragraph reserved for the respective anchor. This is how I set up all my documents, because tables behave so much better this way, for reasons other than API manipulation. I use a simple 2pt format and insert a single table into each one, wherever I need a table. By getting the anchors out of text-containing paragraphs, you could avoid this problem altogether.
I hope this helps some. I wish I could tell you exactly why your code is not working right, but I can't really see it. Also, BTW, the use of the while loop seems a little strange... consider a for loop based on textItems.len instead.
Good luck,
Russ
Copy link to clipboard
Copied
Hi Russ
Thanks very much for taking the time to reply ![]()
I tried your first suggestion of calculating the current text item's end offset based on the next text item's start offset.
It didn't solve my problem but it did point me to the unusual set of circumstances which I believe is causing my problem.
I noticed that all of the tables being wiped were preceded by a paragraph containing a certain word which seems to use unusual characters or glyphs.
The word looks like this:

What seems to be happening is that the FAPI breaks this word into two separate text items which are:
"411" and the strange character immediately preceding it
&
"cobas<weirdchar>e"
So it should require two iterations to properly delete this word, using the loop I showed you before. However, when we perform the first iteration and try to select the first text item, "411" and the strange character immediately preceding it (we're parsing in reverse remember
) F_ApiSetTextRange () actually selects the entire word, therefore causing F_ApiClear () to clear the entire word.
When we go to delete the next text item which should be "cobas<weirdchar>e", we try to select it, but it's already been deleted during the previous iteration so the FAPI actually selects the table after where the word we just deleted was, and wipes it.
I'm wondering what this character is, if the FAPI is capable of handling them, or if there is a workaround in dealing with them
Thanks
Eric
Copy link to clipboard
Copied
Eric,
I don't know. From your picture, the characters look like non-breaking spaces, but if that's what they are, it doesn't explain the behavior you are seeing.
There aren't any variables or other gizmos in that area, are there?
Russ
Copy link to clipboard
Copied
Let me expand on that a bit. If the textItems array is breaking that string, it seems that there must be something in the way. That could be anything, such as a change in character format, a marker, variable, xref, etc. I don't think that any kind of glyph would cause that, but I don't know that for sure.
What I would do is run this through the debugger and look at the .dataType for each text item. That is, for each "index" in textItems.len, take a look at the value of textItems.val[index].dataType to see what it is. I bet that you'll find at least some kind of non-string text item between those two strings you are processing. You'll probably need to manually correlate the dataType integer with its literal constant, unless you write some code for that. Here's the excerpt from fapidefs.h that shows the values in hex:
#define FTI_String 0x00000001 /* StringT */
#define FTI_LineBegin 0x00000002 /* Nada */
#define FTI_LineEnd 0x00000004 /* See flags below */
#define FTI_PgfBegin 0x00000008 /* ID(Pgf) */
#define FTI_PgfEnd 0x00000010 /* ID(Pgf) */
#define FTI_FlowBegin 0x00000020 /* ID(Flow) */
#define FTI_FlowEnd 0x00000040 /* ID(Flow) */
#define FTI_PageBegin 0x00000080 /* ID(Page) */
#define FTI_PageEnd 0x00000100 /* ID(Page) */
#define FTI_SubColBegin 0x00000200 /* ID(SubCol) */
#define FTI_SubColEnd 0x00000400 /* ID(SubCol) */
#define FTI_FrameAnchor 0x00000800 /* ID(AnchoredFrame) */
#define FTI_FnAnchor 0x00001000 /* ID(Footnote) */
#define FTI_TblAnchor 0x00002000 /* ID(Table) */
#define FTI_MarkerAnchor 0x00004000 /* ID(Marker) */
#define FTI_XRefBegin 0x00008000 /* ID(XRef) */
#define FTI_XRefEnd 0x00010000 /* ID(XRef) */
#define FTI_VarBegin 0x00020000 /* ID(Var) */
#define FTI_VarEnd 0x00040000 /* ID(Var) */
#define FTI_ElementBegin 0x00080000 /* ID(Element) */
#define FTI_ElementEnd 0x00100000 /* ID(Element) */
#define FTI_CharPropsChange 0x00200000 /* See flags below */
#define FTI_TextFrameBegin 0x00400000 /* ID(TextFrame) */
#define FTI_TextFrameEnd 0x00800000 /* ID(TextFrame) */
#define FTI_TextObjId 0x01000000
#define FTI_TextInsetBegin 0x02000000
#define FTI_TextInsetEnd 0x04000000 /* ID(FO_TiText),ID(FO_TiFlow)
* ID(FO_TiTextTable) or
* ID(FO_TiApiClient.
*/
#define FTI_ElemPrefixBegin 0x08000000 /* ID(Element) */
#define FTI_ElemPrefixEnd 0x10000000 /* ID(Element) */
#define FTI_ElemSuffixBegin 0x20000000 /* ID(Element) */
#define FTI_ElemSuffixEnd 0x40000000 /* ID(Element)
Russ
Copy link to clipboard
Copied
Hi Russ
I resolved it!
It seems that when I made the initial call to get the text items for that paragraph, that list remains static even after deletions are performed, so
F_ApiSetTextRange was working with old information.
I added a line of code at the bottom of my loop to keep the text item list up to date as deletions are performed. So even though the selection being performed still doesnt match the beginning and end offsets in the textrange for the current text item, in the next loop iteration, the API will not try and grab text it thinks exists but no longer does.
So my deletion loop looks like this:
//we go through all the paragraph text items
//removing everything except crossreferences
nItem = (int)textItems.len;
while (textItems.len > 0 && nItem >= 0)
{
F_TextItemT thisTextItem = textItems.val[nItem];
if (thisTextItem.dataType == FTI_String)
{
StringT string = thisTextItem.u.sdata;
CString szText = ConvertFromStringT(string);
F_TextRangeT range;
range.beg.objId = hParagraph;
range.end.objId = hParagraph;
range.beg.offset = thisTextItem.offset;
int nTextLength = szText.GetLength();
range.end.offset = thisTextItem.offset + nTextLength;
F_ApiSetTextRange(FV_SessionId, m_hDocument, FP_TextSelection, &range);
F_ApiClear(m_hDocument, 0);
}
else if (thisTextItem.dataType == FTI_LineEnd)
{
if (thisTextItem.u.idata == FTI_HardLineEnd)
{
F_TextRangeT range;
range.beg.objId = hParagraph;
range.end.objId = hParagraph;
range.beg.offset = thisTextItem.offset;
range.end.offset = thisTextItem.offset+1;
F_ApiSetTextRange(FV_SessionId, m_hDocument, FP_TextSelection, &range);
F_ApiClear(m_hDocument, 0);
}
}
// Skip cross refs i.e. leave them in place,
// we will cut them out and replace them later when we find the tag
else if (thisTextItem.dataType == FTI_XRefEnd)
{
while (thisTextItem.dataType != FTI_XRefBegin && nItem >= 0)
{
nItem--;
thisTextItem = textItems.val[nItem];
}
}
/* update text item list to reflect deletions we just made*/
textItems = F_ApiGetText(m_hDocument, hParagraph, -1);
nItem--;
}
Hopefully this is a robust solution! Those mystery characters in the string I discussed in my previous post are device control characters, with a value of 0x11. I'm not too sure of their purpose, but I don't need to worry about them now anyway it seems.
Thanks
Eric
Copy link to clipboard
Copied
Ah yes, that makes sense. I should have thought of that, since you are making multiple changes in the same span of text. Indeed, that textItems list is just a statically-populated data structure... it has no dynamic link with the original document after it is allocated.
One more thing... you probably know what you are doing here, but I'd like to add a cautionary note about copying pointers, such as in:
StringT string = thisTextItem.u.sdata;
If you aren't careful with these, it is super-easy to get crashes and/or memory leaks. In my personal work, I would either just use the array member itself, or copy the item and deallocate it when done. Such as:
strBuf = F_StrCopyString(thisTextItem.u.sdata);
. . .
F_ApiDeallocateString(&strBuf);
Having said that, I'm not a developer by trade, so it's certainly probable that your methodology is fine for someone who knows what they are doing.
Russ
Copy link to clipboard
Copied
Hi Russ
Thanks for that.
In terms of knowing what I'm doing, I'm constantly learning new things
about what I can and can't do with the API. We use the FAPI as part
of our computer assisted translation product, Alchemy Publisher.
Have a quick google if you want to find out more about how it works.
I started working on the Frame API once most of the work was done.
We were having lots of niggling issues which, although individually are minor,
collectively cause serious deterioration of document output fidelity.
I wasn't aware that pointer copying like that were so risky! Your approach
of string copying seems like the safest for sure, so I'll make sure to replace
pointer assignment with you line of code.
Thanks very much for all your help, I doubt I would have solved my issue without.
Eric
Copy link to clipboard
Copied
Great. good luck. By the way, if you are in the mood to take my amateur advice, I would replace this line too:
F_TextItemT thisTextItem = textItems.val[nItem];
maybe something like:
F_TextItemT thisTextItem = F_ApiCopyTextItem(&textItems.val[nItem]);
... then later ...
F_ApiDeallocateTextItem(&thisTextItem);
...and don't forget the main array as well, when you are done with it:
F_ApiDeallocateTextItems(&textItems);
The FDK/FM API is very robust and reliable, but improper memory handling is truly its Achilles heel. It is a sure path to leaks and crashes.
Russ
Get ready! An upgraded Adobe Community experience is coming in January.
Learn more