Получение встроенных изображений из Lotus Notes с помощью lotusscript

У меня есть NotesDocument, где в некоторых полях RichText есть как текст, так и встроенные изображения. Я могу получить текстовую часть этих элементов, но не могу получить встроенные изображения с помощью lotusscript. Может ли кто-нибудь предложить мне способ извлечения встроенных изображений из этих документов. Код LotusScript:

Sub Click(Source As Button)
    Dim session As New NotesSession   
    Dim db As NotesDatabase   
    Dim mainDoc As NotesDocument
    Dim v As NotesView   
    Set db = session.CurrentDatabase   

    Dim fileName As String
    Dim fileNum As Integer
    fileNum% = Freefile()
    fileName$ = "D:\data.txt"
    Open FileName$ For Append As fileNum%

    Set v = db.GetView("MyView")
    Set mainDoc = v.GetFirstDocument       

    While Not ( mainDoc Is Nothing )              
        Forall i In mainDoc.Items
            If i.Type = RICHTEXT Then
                 Write #fileNum% ,    i.Name & ":" & i.text  'how the images??
            End If
        End Forall              
        Set mainDoc = v.GetNextDocument( mainDoc )  
    Wend
End Sub

Спасибо.


person Nazrul    schedule 24.10.2010    source источник
comment
Один из способов, которым мне удалось это сделать в тот день, заключался в открытии документа через задачу HTTP (в виде веб-страницы), а затем я мог загружать изображения. Грязно, но выполнимо и, возможно, проще, чем другие хаки, в зависимости от вашего опыта.   -  person Sam Sirry    schedule 17.01.2021


Ответы (4)


Midas - самый простой способ сделать это, но он не бесплатный. (Это более чем стоит потраченных денег с точки зрения общей экономии времени, но если ваша организация похожа на ту, в которой я работал, вся стоимость инструмента будет переложена на счетную единицу, которая владеет текущим проектом, скорее чем они амортизируются по всей организации, и они, вероятно, изменят свои требования, прежде чем согласиться с затратами.) Есть другой подход, и это экспорт базы данных в DXL (Domino XML) с использованием опции экспорта ConvertNotesBitmapToGIF. Изображения появятся в XML как <picture> элементы с данными в кодировке Base64. Если вы полностью работаете в среде Notes, вам необходимо создать временный документ с полем форматированного текста, используемым в качестве NotesMIMEEntity, чтобы преобразовать закодированное изображение в двоичное перед его потоковой передачей в файл (с использованием NotesStream). Все это предполагает, что вы работаете с версией 6 или выше; если вы используете R5 или более раннюю версию, Midas или прямой доступ к записям компакт-диска с помощью C API - единственный способ летать.

person Stan Rogers    schedule 08.11.2010

Семь лет спустя я выдергивал волосы из-за этого. Ответ Rod H касается вложений, но встроенные изображения - это совсем другое.

Больше всего мне повезло с кодом LotusScript Gold Collection от @andre-guirard, который находится здесь: https://www.openntf.org/main.nsf/project.xsp?r=project/LotusScript%20Gold%20Collection Однако это не все, потому что не обрабатывать документы, в которые встроенные изображения были встроены по старинке. (В Notes изменился способ хранения встроенных изображений.)

Я очень старался объединить его с информацией AGECOM, представленной здесь: https://www.agecom.com.au/support/agecomkb.nsf/0/58cbf10f0ab723c9ca25803e006c7de8?OpenDocument, изменив объект EmbeddedImage Андре для беспрепятственной обработки обоих форматов, проверив, является ли встроенное изображение в поле форматированного текста на самом деле просто указатель на поле $ FILE, а затем, если так, получение объекта FileItem, но в конце концов я исчерпал свое понимание и возможности до такой степени, что я не мог оправдать трату ресурсов моего работодателя (мое время) на это.

Так что, если у вас есть встроенные изображения, содержащиеся по-новому, я думаю, что код Андре будет работать без проблем. В противном случае я старался изо всех сил, но у меня нет ответа ... У меня есть то, что (для меня) тупик, представленный в надежде, что вы или кто-то другой, кто наткнется на него, сможете смущать меня, объясняя, что я делаю не так!

По сути, я начал с кода Андре и изменил его следующим образом ...

В DOMUtils добавьте следующий метод:

%REM
    Function DU_GetMeOrNextSiblingWithAttr
    Description: Starting with a particular node, return that node or the next sibling with an attribute that has a particular value.
        Does not recurse into the tree; looks only at the node passed and later siblings. 
    Parameters:
        nodeStart: node to start your search with.
        targetElement: element name of desired node.
        attrName: attribute name you want to check.
        attrValue: attribute value of element you're looking for.
        flags: string-matching flags to compare attribute, e.g. 1 for case insensitive.
%END REM
Function DU_GetMeOrNextSiblingWithAttr(nodeStart As NotesDOMNode, ByVal targetElement$, ByVal attrName$, ByVal attrValue$, ByVal flags%) As NotesDOMElementNode
    Dim node As NotesDOMNode, elTmp As NotesDOMElementNode
    Set node = nodeStart
    Do Until node.Isnull
        If node.Nodetype = DOMNODETYPE_ELEMENT_NODE Then
            If node.Nodename = targetElement Then
                Set elTmp = node
                If StrComp(elTmp.Getattribute(attrName), attrValue, flags) = 0 Then
                    Set DU_GetMeOrNextSiblingWithAttr = elTmp
                    Exit Function
                End If
            End If
        End If
        Set node = node.Nextsibling
    Loop
End Function

Замените FileItem.New следующим кодом:

    %REM
    Sub New
    Description: Arguments are the parsed DOM node of the element representing a
        design element, and the name of the composite item you would like to read,
        modify or create.
%END REM
Sub New(parent As FileItemParent, elNote As NotesDOMElementNode, itemName$, fileName$)
    Set m_elNote = elNote
    
    SetItem elNote, itemName$, fileName$
    

    Dim node As NotesDOMNode
    Set node = m_elNote.Parentnode
    While node.Nodetype <> DOMNODETYPE_DOCUMENT_NODE
        Set node = node.Parentnode
    Wend
    Set m_domd = node
    parent.RegisterFileItem Me ' make sure the design element knows about us.
        ' (in case someone gets smart and invokes the constructor directly
        ' instead of using the nice methods we've provided).
End Sub

%REM
    Sub SetItem
<!-- Created Dec 6, 2017 by JSmart523 -->
    If fileName$ is blank, returns the XPath equivalent of elNote/ancestor::document/item[@name=itemName$][position()=1]
    If fileName$ is not blank, returns the XPath equivalent of elNote/ancestor::document/item[@name=itemName$][object/file/@name=fileName$][position()=1]
    
    Case insensitive. Changes itemName$ and fileName$ to the correct case if found.

    Also sets Me.m_elItem to the returned NotesDOMElementNode
    Also sets Me.m_elRawData to the file contents
%END REM
Sub SetItem(elNote As NotesDOMElementNode, itemName$, fileName$)
    Dim elFile As NotesDOMElementNode
    Dim node As NotesDOMNode
    
    'set node to ancestor::document
    Set node = elNote
    Do Until node.NodeName = "document"
        Set node = node.ParentNode
    Loop
    
    'If fileName$ = "", get the first ancestor::document/item[@name=itemName$]
    'Otherwise,         get the first ancestor::document/item[@name=itemName$][/object/file/@name=fileName$]
    Set m_elItem = DU_GetChildOfType(node, DOMNODETYPE_ELEMENT_NODE)
    QualifyingItem m_elItem, itemName$, m_elRawData, fileName$
    m_itemName = itemName$
    m_fileName = fileName$
End Sub

%REM
    Sub QualifyingItem
<!-- Created Dec 8, 2017 by JSmart523 -->
    Starting with incoming elItem node, ensures it's an item we want or changes elItem to the first sibling that qualifies.
%END REM
Sub QualifyingItem(elItem As NotesDOMElementNode, itemName$, elRawData As NotesDOMElementNode, fileName$)
    Dim elFile As NotesDOMElementNode
    Dim node As NotesDOMNode
    Dim elObject As NotesDOMElementNode

    If Not elItem Is Nothing Then
        'Initially, elItem is just a starting point, not necessarily the item we want.
        'If it's an item with the right name, great, otherwise change elItem to the next sibling item with the right name. 
        Set elItem = DU_GetMeOrNextSiblingWithAttr(elItem, "item", "name", itemName$, 1)
        
        If Not elItem Is Nothing Then
            If fileName$ = "" Then
                'we have the right item, and aren't looking for a file node, which means we want the rawitemdata node
                Set elRawData = DU_getChildNamed("rawitemdata", elItem)
            Else
                'We are looking for a $FILE item that contains a file.
                'There are possibly several $FILE items within a document, one for each file. We've got the right one if ./object/file/@name = fileName$
                Do
                    Set elObject = DU_GetChildNamed("object", elItem)
                    If Not elObject Is Nothing Then
                        Set elFile = DU_GetChildWithAttr(elObject, "file", "name", fileName$, 1)
                        If Not elFile Is Nothing Then
                            'Yay! We have the right elItem node!
                            Set elRawData = DU_GetChildNamed("filedata", elFile)
                            fileName$ = elFile.GetAttribute("name")
                            Exit Do
                        End If
                    End If
                    Set elItem = DU_GetMeOrNextSiblingWithAttr(elItem.NextSibling, "item", "name", itemName$, 1)
                Loop Until elItem Is Nothing
                'At this point, either we jumped out of the loop with a valid elItem and elRawData, or elItem is Nothing
            End If
        End If
    End If
    
    If elItem Is Nothing Then
        'we didn't find the correct item
        'make sure elRawData is changed to Nothing, too.
        Set elRawData = Nothing
    Else
        itemName$ = elItem.GetAttribute("name")
    End If
End Sub

Также в библиотеке сценариев FileItem добавьте новый класс FileItemParent

    %REM
    Class FileItemParent
<!-- Created Dec 5, 2017 by JSmart523 -->
    This is a base class for objects that use FileItem objects
%END REM
Class FileItemParent
    m_elElRoot As NotesDOMElementNode
    m_elFD As NotesDOMElementNode
    Public m_fileItem As FileItem
    m_fItems List As FileItem ' list of FileItems we've created and returned to caller.
    m_iMode As Integer

    %REM
        Property Get DOMElement
        Description: Return the element node representing the design element.
    %END REM
    Public Property Get DOMElement As NotesDOMElementNode
        Set DOMElement = m_elElRoot
    End Property

    %REM
        Sub New
        Arguments:
            db: the database containing the design element.
            elElement: the DOM element corresponding to the design note (e.g. the <note>
                element).
            domp: The DOM parser object containing elElement.
    %END REM
    Sub New(elElement As NotesDOMElementNode)
        Set m_elElRoot = elElement
    End Sub
    
    Sub Delete
        On Error Resume Next
        ForAll thing In m_fItems
            Delete thing
        End ForAll
    End Sub
    
    %REM
        Function HasItem
        Description: Determine whether there's an item element in the note DXL with a
            given item name.
            Note that the presence of an item doesn't guarantee it's formatted as a file
            CD record.
    %END REM
    Function HasItem(ByVal itemName$) As Boolean
        HasItem = Not (DU_GetChildWithAttr(m_elElRoot, "item", "name", itemName, 1) Is Nothing)
    End Function

    
    %REM
        Function RegisterFileItem
        Description: For internal use -- lets the FileItem class notify us that it's
            referencing our DOM tree so that we can delete the object if we erase the
            corresponding item element.
    %END REM
    Sub RegisterFileItem(x As FileItem)
        Set m_fItems(LCase(x.itemName)) = x
        If m_FileItem Is Nothing Then
            Set m_FileItem = x
        End If
    End Sub
    
    %REM
        Function GetFileItem
        Description: Retrieve the FileItem object associated with a CD-record item.
            An object will be returned even if the item doesn't exist, which you can
            use to create the item via UpdateFile method.
    %END REM
    Function GetFileItem(itemName$, fileName$) As FileItem
        Set GetFileItem = New FileItem(Me, m_elElRoot, itemName, fileName)
    End Function
    
End Class

Класс FileItemParent - это в первую очередь код, взятый из класса FileResource Андре, поэтому его могут использовать как FileResource, так и EmbeddedImage. Измените FileResource, чтобы расширить FileItemParent, удалив любой повторяющийся код.

Теперь мы хотим изменить EmbeddedImage, чтобы, даже если узел встроенного изображения содержал ссылку на элемент $ FILE, а не фактическое содержимое, возвращал фактическое содержимое.

Итак, измените EmbeddedImage, чтобы расширить FileItemParent

Добавьте / замените следующие методы в EmbededImage

%REM
    Sub InitFileItem
<!-- Created Dec 6, 2017 by JSmart523 -->
    Called by New
%END REM
Sub InitFileItem()
    Dim buffer As Variant 'byte array
    Dim iFileNameLen As Integer
    Dim sFileName As String
    Dim sItemName As String
    Dim stream As NotesStream
    
    If Len(m_b64) < 30000 Then
        'If content is short then maybe it's a link to a $FILE item instead of the actual content?
        Dim session As New NotesSession
        Set stream = session.CreateStream()
        Base64ToBinary m_b64, stream
        stream.Position = 0
        buffer = stream.Read(1)
        If buffer(0) = 196 Then
            'this is a link to a $FILE, not the actual image contents!
            
            stream.Position = 10
            buffer = stream.Read(2)
            iFileNameLen = ConvertWordByteArray(buffer)
            
            stream.Position = 24
            buffer = stream.Read(iFileNameLen)
            sFileName = BytesToString(buffer)
            sItemName = "$FILE"
            
            GetFileItem sItemName, sFileName 'sets m_fileItem to a FileItem object
        End If
    End If
End Sub

%REM
    Property Get SuggestedFileName
%END REM
Public Property Get SuggestedFileName As String
    If m_fileItem Is Nothing Then
        SuggestedFileName = "Embedded-" + ItemName + "." + SuggestedFileType
    Else
        SuggestedFileName = m_fileItem.FileName
        If InStr(SuggestedFileName, ".") = 0 Then
            SuggestedFileName = SuggestedFileName + "." + SuggestedFileType
        End If
    End If
End Property

%REM
    Property Get SuggestedFileType
%END REM
Public Property Get SuggestedFileType As String
    If ImageType = "notesbitmap" Then
        SuggestedFileType = "bmp"
    Else
        SuggestedFileType = ImageType
    End If
End Property

%REM
    Sub ReadFileToStream
%END REM
Sub ReadFileToStream(streamOut As NotesStream)
    If m_FileItem Is Nothing Then
        ReadToStream streamOut
    Else
        Set m_FileItem.Stream = streamOut
        m_FileItem.Load
    End If
End Sub

а затем измените EmbeddedItem.New, чтобы в конце вызвать InitFileItem, чтобы, если это ссылка, при получении содержимого возвращалось содержимое, а не ссылка.

Хорошо, насколько я знаю, пока все хорошо, но проблема в том, что CD-записи встроенных изображений, хранящихся в элементах $ FILE (т.е. узел встроенного изображения поля форматированного текста просто содержит ссылку, а не фактическое изображение), были / были задокументированы таким образом, что для меня это было непонятно, несмотря на код и пояснения AGECOM. Я мог бы использовать приведенный выше код и объект Андре EmbeddedImageList для захвата каждого встроенного изображения, но я просто не мог заставить работать метод ConvertOldCDToNew, поэтому я не мог преобразовать старый формат записи компакт-диска в твердые, неповрежденные файлы! Я не знаю, удалял ли я слишком много байтов, не удаляя нужные, или, может быть, я просто забыл взять их с собой!

person JSmart523    schedule 22.01.2018

Предлагаю вам взглянуть на продукт Genii Software MidasLSX. Они предлагают пакет расширений LotusScript, которые упрощают работу со сложными элементами Lotus Notes Rich Text.

http://www.geniisoft.com/showcase.nsf/MidasHelp

В противном случае вы можете поэкспериментировать с классом NotesRichTextNavigator, чтобы получить доступ к изображению в элементе форматированного текста (теоретически). По этому поводу очень мало документации. Я не мог точно сказать, какое изображение будет выглядеть при использовании этого класса, но, если предположить, что вы перемещаетесь по элементу форматированного текста и можете получить дескриптор изображения как NotesEmbeddedObject, я знаю, что есть способ сохранить объект на диск из этот класс.

Другая (сумасшедшая) мысль - отправить документ по электронной почте и получить его другой программой, которая может более легко обработать тело электронного письма. Заметки просто не очень полезны при обработке собственных полей форматированного текста.

person Ken Pespisa    schedule 25.10.2010

Вот агент, который я использую для отсоединения файлов от поля Richtext в моих документах.

Option Public
Dim uidoc As notesuidocument
Dim doc As NotesDocument
Dim db As NotesDatabase
Dim obj As NotesEmbeddedObject
Dim collection As NotesDocumentCollection
Dim rt As Variant
Dim attachNames As Variant
Dim i As Integer, x As Integer
Dim j As Integer
' Agent  - Detach Attachments to C drive for later reattachment
' Copies all attachment in richtext field to personal directory.

Sub Initialize
    Dim ws As New notesuiworkspace
    Dim ses As New NotesSession
    Set db = ses.CurrentDatabase
    Set collection = db.UnprocessedDocuments
    '  get first doc in collection
    For j = 1 To collection.Count
        Set doc = collection.GetNthDocument( j )    
' ---  create array of filenames of all the attachments in the document
        i = 0
        Redim attachNames(i)
        Forall x In doc.items
            If x.name = "$FILE" Then
                attachNames(i) = x.values(0)
                i = i + 1
                Redim Preserve attachNames(i)
            End If
        End Forall

        If i > 0 Then
            Redim Preserve attachNames(i-1)
        End If

' ---  for all of the filenames in attachNames, if it exists in the rich text field, detatch them
        If doc.hasItem("richtextfieldname") Then
            Set rt = doc.GetFirstItem("richtextfieldname")
        End If
        If attachNames(0) <> "" Then
            Forall x In attachNames 
                Set obj = rt.GetEmbeddedObject( x )
                If Not( obj Is Nothing ) Then
                    Call obj.ExtractFile( "C:\path\" & Cstr(x) )
                End If
            End Forall
        End If
        Call doc.save(True, False)
    Next
End Sub
person Rod H    schedule 05.12.2012