942 lines
14 KiB
C++
942 lines
14 KiB
C++
/*++
|
|
|
|
Copyright (c) 1994-1999 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
mime.cpp
|
|
|
|
Abstract:
|
|
|
|
Mime mapping dialog
|
|
|
|
Author:
|
|
|
|
Ronald Meijer (ronaldm)
|
|
|
|
Project:
|
|
|
|
Internet Services Manager
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
|
|
//
|
|
// Include Files
|
|
//
|
|
#include "stdafx.h"
|
|
#include "common.h"
|
|
#include "resource.h"
|
|
#include "mime.h"
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
|
|
|
|
CMimeEditDlg::CMimeEditDlg(
|
|
IN CWnd * pParent OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor to create new mime mapping
|
|
|
|
Arguments:
|
|
|
|
CWnd * pParent : Optional parent window or NULL
|
|
|
|
Return Value:
|
|
|
|
N/A
|
|
|
|
--*/
|
|
: m_strExt(),
|
|
m_strMime(),
|
|
CDialog(CMimeEditDlg::IDD, pParent)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
CMimeEditDlg::CMimeEditDlg(
|
|
IN LPCTSTR lpstrExt,
|
|
IN LPCTSTR lpstrMime,
|
|
IN CWnd * pParent OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor to edit existing mime mapping
|
|
|
|
Arguments:
|
|
|
|
LPCTSTR lpstrExt : Extension
|
|
LPCTSTR lpstrMime : Mime mapping
|
|
CWnd * pParent : Optional parent window or NULL
|
|
|
|
Return Value:
|
|
|
|
N/A
|
|
|
|
--*/
|
|
: m_strExt(lpstrExt),
|
|
m_strMime(lpstrMime),
|
|
CDialog(CMimeEditDlg::IDD, pParent)
|
|
{
|
|
//{{AFX_DATA_INIT(CMimeEditDlg)
|
|
//}}AFX_DATA_INIT
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeEditDlg::DoDataExchange(
|
|
IN CDataExchange * pDX
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialise/Store control data
|
|
|
|
Arguments:
|
|
|
|
CDataExchange * pDX - DDX/DDV control structure
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
CDialog::DoDataExchange(pDX);
|
|
|
|
//{{AFX_DATA_MAP(CMimeEditDlg)
|
|
DDX_Control(pDX, IDOK, m_button_Ok);
|
|
DDX_Control(pDX, IDC_EDIT_MIME, m_edit_Mime);
|
|
DDX_Control(pDX, IDC_EDIT_EXTENT, m_edit_Extent);
|
|
//}}AFX_DATA_MAP
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Message Map
|
|
//
|
|
BEGIN_MESSAGE_MAP(CMimeEditDlg, CDialog)
|
|
//{{AFX_MSG_MAP(CMimeEditDlg)
|
|
//}}AFX_MSG_MAP
|
|
|
|
ON_EN_CHANGE(IDC_EDIT_MIME, OnItemChanged)
|
|
ON_EN_CHANGE(IDC_EDIT_EXTENT, OnItemChanged)
|
|
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
|
|
void
|
|
CMimeEditDlg::SetControlStates()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enable/disable controls depending on current dialog data
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
m_button_Ok.EnableWindow(
|
|
m_edit_Extent.GetWindowTextLength() > 0
|
|
&& m_edit_Mime.GetWindowTextLength() > 0
|
|
);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Message Handlers
|
|
//
|
|
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
|
|
|
|
void
|
|
CMimeEditDlg::OnItemChanged()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Respond to changes
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
SetControlStates();
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
CMimeEditDlg::OnInitDialog()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
WM_INITDIALOG handler. Initialize the dialog.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if no focus is to be set automatically, FALSE if the focus
|
|
is already set.
|
|
|
|
--*/
|
|
{
|
|
CDialog::OnInitDialog();
|
|
|
|
m_edit_Extent.SetWindowText(m_strExt);
|
|
m_edit_Mime.SetWindowText(m_strMime);
|
|
|
|
SetControlStates();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeEditDlg::OnOK()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
'OK' button was pressed -- store data.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if no focus is to be set automatically, FALSE if the focus
|
|
is already set.
|
|
|
|
--*/
|
|
{
|
|
m_edit_Extent.GetWindowText(m_strExt);
|
|
m_edit_Mime.GetWindowText(m_strMime);
|
|
|
|
CleanExtension(m_strExt);
|
|
|
|
CDialog::OnOK();
|
|
}
|
|
|
|
|
|
|
|
|
|
CMimeDlg::CMimeDlg(
|
|
IN CStringListEx & strlMimeTypes,
|
|
IN CWnd * pParent OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Constructor for the MIME listing dialog
|
|
|
|
Arguments:
|
|
|
|
CStringListEx & strlMimeTypes : Listing of mime types to edit
|
|
CWnd * pParent : Optional parent window or NULL
|
|
|
|
Return Value:
|
|
|
|
N/A
|
|
|
|
--*/
|
|
: m_fDirty(FALSE),
|
|
m_strlMimeTypes(strlMimeTypes),
|
|
CDialog(CMimeDlg::IDD, pParent)
|
|
{
|
|
//{{AFX_DATA_INIT(CMimeDlg)
|
|
//}}AFX_DATA_INIT
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::DoDataExchange(
|
|
IN OUT CDataExchange * pDX
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialise/Store control data
|
|
|
|
Arguments:
|
|
|
|
CDataExchange * pDX - DDX/DDV control structure
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
CDialog::DoDataExchange(pDX);
|
|
//{{AFX_DATA_MAP(CMimeDlg)
|
|
DDX_Control(pDX, IDOK, m_button_Ok);
|
|
DDX_Control(pDX, IDC_EDIT_EXTENSION, m_edit_Extention);
|
|
DDX_Control(pDX, IDC_EDIT_CONTENT_TYPE, m_edit_ContentType);
|
|
DDX_Control(pDX, IDC_BUTTON_REMOVE_MIME, m_button_Remove);
|
|
DDX_Control(pDX, IDC_BUTTON_EDIT_MIME, m_button_Edit);
|
|
//}}AFX_DATA_MAP
|
|
|
|
DDX_Control(pDX, IDC_LIST_MIME_TYPES, m_list_MimeTypes);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Message Map
|
|
//
|
|
BEGIN_MESSAGE_MAP(CMimeDlg, CDialog)
|
|
//{{AFX_MSG_MAP(CMimeDlg)
|
|
ON_BN_CLICKED(IDC_BUTTON_EDIT_MIME, OnButtonEdit)
|
|
ON_BN_CLICKED(IDC_BUTTON_NEW_TYPE, OnButtonNewType)
|
|
ON_BN_CLICKED(IDC_BUTTON_REMOVE_MIME, OnButtonRemove)
|
|
ON_LBN_DBLCLK(IDC_LIST_MIME_TYPES, OnDblclkListMimeTypes)
|
|
ON_LBN_SELCHANGE(IDC_LIST_MIME_TYPES, OnSelchangeListMimeTypes)
|
|
//}}AFX_MSG_MAP
|
|
|
|
ON_EN_CHANGE(IDC_EDIT_CONTENT_TYPE, OnItemChanged)
|
|
ON_EN_CHANGE(IDC_EDIT_EXTENSION, OnItemChanged)
|
|
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::SetControlStates()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enable/disable controls depending on current dialog data
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
m_button_Remove.EnableWindow(m_list_MimeTypes.GetSelCount() > 0);
|
|
m_button_Edit.EnableWindow(m_list_MimeTypes.GetSelCount() == 1);
|
|
m_button_Ok.EnableWindow(m_fDirty);
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
CMimeDlg::BuildDisplayString(
|
|
IN CString & strIn,
|
|
OUT CString & strOut
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Build a listbox-suitable display string for the mime type
|
|
|
|
Arguments:
|
|
|
|
CString & strIn : Input string in metabase format
|
|
CString & strOut : Output string in display format
|
|
|
|
Return Value:
|
|
|
|
TRUE if successfull, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
|
|
int nComma = strIn.Find(_T(','));
|
|
if (nComma >= 0)
|
|
{
|
|
CString strExt = strIn.Left(nComma);
|
|
CString strMime = strIn.Mid(nComma + 1);
|
|
|
|
try
|
|
{
|
|
BuildDisplayString(strExt, strMime, strOut);
|
|
++fSuccess;
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
TRACEEOLID("Mem exception in BuildDisplayString");
|
|
e->ReportError();
|
|
e->Delete();
|
|
}
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
CMimeDlg::CrackDisplayString(
|
|
IN CString & strIn,
|
|
OUT CString & strExt,
|
|
OUT CString & strMime
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Parse a display-formatted mime mapping string, and break into
|
|
component parts
|
|
|
|
Arguments:
|
|
|
|
CString & strIn : Input string in display format
|
|
CString & strExt : Output extension string
|
|
CString & strMime : Output MIME string.
|
|
|
|
|
|
Return Value:
|
|
|
|
TRUE if successfull, FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
|
|
try
|
|
{
|
|
int nTab = strIn.Find(_T('\t'));
|
|
if (nTab >= 0)
|
|
{
|
|
strExt = strIn.Left(nTab);
|
|
strMime = strIn.Mid(nTab + 1);
|
|
|
|
++fSuccess;
|
|
}
|
|
}
|
|
catch(CMemoryException * e)
|
|
{
|
|
TRACEEOLID("Mem exception in CrackDisplayString");
|
|
e->ReportError();
|
|
e->Delete();
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
CMimeDlg::FindMimeType(
|
|
IN const CString & strTargetExt
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Find a mime type by its extention. The return value
|
|
is the listbox index where the item may be found, or
|
|
-1 if the item doesn't exist
|
|
|
|
Arguments:
|
|
|
|
const CString & strTargetExt : Target extension we're searching for
|
|
|
|
Return Value:
|
|
|
|
The index of the MIME mapping for this extension if found, or -1
|
|
otherwise.
|
|
|
|
--*/
|
|
{
|
|
CString str;
|
|
CString strExt;
|
|
CString strMime;
|
|
|
|
//
|
|
// CODEWORK: Change to binsearch
|
|
//
|
|
for (int n = 0; n < m_list_MimeTypes.GetCount(); ++n)
|
|
{
|
|
m_list_MimeTypes.GetText(n, str);
|
|
if (CrackDisplayString(str, strExt, strMime))
|
|
{
|
|
if (!strExt.CompareNoCase(strTargetExt))
|
|
{
|
|
//
|
|
// Found it.
|
|
//
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Not found
|
|
//
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::FillListBox()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Move the mime mappings from the string list to
|
|
the listbox
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
BeginWaitCursor();
|
|
|
|
POSITION pos = m_strlMimeTypes.GetHeadPosition();
|
|
|
|
while(pos)
|
|
{
|
|
CString & str = m_strlMimeTypes.GetNext(pos);
|
|
CString strOut;
|
|
|
|
if (BuildDisplayString(str, strOut))
|
|
{
|
|
m_list_MimeTypes.AddString(strOut);
|
|
}
|
|
}
|
|
|
|
EndWaitCursor();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::FillFromListBox()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reverse the above; Move the contents of the listbox
|
|
back to the stringlist
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
CString str;
|
|
CString strExt;
|
|
CString strMime;
|
|
|
|
BeginWaitCursor();
|
|
|
|
m_strlMimeTypes.RemoveAll();
|
|
|
|
for (int n = 0; n < m_list_MimeTypes.GetCount(); ++n)
|
|
{
|
|
m_list_MimeTypes.GetText(n, str);
|
|
if (CrackDisplayString(str, strExt, strMime))
|
|
{
|
|
BuildMetaString(strExt, strMime, str);
|
|
m_strlMimeTypes.AddTail(str);
|
|
}
|
|
}
|
|
|
|
EndWaitCursor();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Message Handlers
|
|
//
|
|
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
|
|
|
|
BOOL
|
|
CMimeDlg::OnInitDialog()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
WM_INITDIALOG handler. Initialize the dialog.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if no focus is to be set automatically, FALSE if the focus
|
|
is already set.
|
|
|
|
--*/
|
|
{
|
|
CDialog::OnInitDialog();
|
|
|
|
m_list_MimeTypes.Initialize();
|
|
|
|
FillListBox();
|
|
SetControlStates();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::OnButtonEdit()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
'Edit' button has been pressed -- edit current selection
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
int nCurSel = m_list_MimeTypes.GetCurSel();
|
|
|
|
if (nCurSel >= 0)
|
|
{
|
|
CString str;
|
|
CString strExt;
|
|
CString strMime;
|
|
|
|
m_list_MimeTypes.GetText(nCurSel, str);
|
|
|
|
if (CrackDisplayString(str, strExt, strMime))
|
|
{
|
|
CMimeEditDlg dlg(strExt, strMime, this);
|
|
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
strExt = dlg.m_strExt;
|
|
strMime = dlg.m_strMime;
|
|
|
|
BuildDisplayString(strExt, strMime, str);
|
|
m_list_MimeTypes.DeleteString(nCurSel);
|
|
nCurSel = m_list_MimeTypes.AddString(str);
|
|
m_list_MimeTypes.SetCurSel(nCurSel);
|
|
m_fDirty = TRUE;
|
|
|
|
OnSelchangeListMimeTypes();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::OnButtonNewType()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
'New' button has been pressed. Create new MIME mapping, and
|
|
bring up configuration on it.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
CMimeEditDlg dlg(this);
|
|
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
CString str;
|
|
CString strExt = dlg.m_strExt;
|
|
CString strMime = dlg.m_strMime;
|
|
|
|
//
|
|
// Check to see if this extension already existed
|
|
// in the list
|
|
//
|
|
int nOldSel = FindMimeType(strExt);
|
|
if (nOldSel >= 0)
|
|
{
|
|
//
|
|
// Yes, ask to have it replaced
|
|
//
|
|
if (::AfxMessageBox(
|
|
IDS_REPLACE_MIME,
|
|
MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2
|
|
) == IDYES)
|
|
{
|
|
//
|
|
// Kill the old one
|
|
//
|
|
m_list_MimeTypes.DeleteString(nOldSel);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Nope..
|
|
//
|
|
return;
|
|
}
|
|
}
|
|
|
|
BuildDisplayString(strExt, strMime, str);
|
|
int nCurSel = m_list_MimeTypes.AddString(str);
|
|
m_list_MimeTypes.SetCurSel(nCurSel);
|
|
m_fDirty = TRUE;
|
|
|
|
OnSelchangeListMimeTypes();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::OnButtonRemove()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
'Remove' button has been pressed. Remove the current MIME mapping.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
if (::AfxMessageBox(
|
|
IDS_REMOVE_MIME,
|
|
MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2
|
|
) != IDYES)
|
|
{
|
|
//
|
|
// Changed his mind
|
|
//
|
|
return;
|
|
}
|
|
|
|
int nCurSel = m_list_MimeTypes.GetCurSel();
|
|
|
|
int nSel = 0;
|
|
while (nSel < m_list_MimeTypes.GetCount())
|
|
{
|
|
if (m_list_MimeTypes.GetSel(nSel))
|
|
{
|
|
m_list_MimeTypes.DeleteString(nSel);
|
|
m_fDirty = TRUE;
|
|
continue;
|
|
}
|
|
|
|
++nSel;
|
|
}
|
|
|
|
if (m_fDirty)
|
|
{
|
|
if (nCurSel > 0)
|
|
{
|
|
--nCurSel;
|
|
}
|
|
|
|
m_list_MimeTypes.SetCurSel(nCurSel);
|
|
OnSelchangeListMimeTypes();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::OnItemChanged()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Respond to changes
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
SetControlStates();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::OnDblclkListMimeTypes()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Double clicking on an item is the same as pressing edit
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
OnButtonEdit();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::OnSelchangeListMimeTypes()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Respond to change in selection in the listbox.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Update the text in the description box
|
|
//
|
|
int nCurSel = m_list_MimeTypes.GetCurSel();
|
|
|
|
if (nCurSel >= 0)
|
|
{
|
|
CString str;
|
|
CString strExt;
|
|
CString strMime;
|
|
|
|
m_list_MimeTypes.GetText(nCurSel, str);
|
|
|
|
if (CrackDisplayString(str, strExt, strMime))
|
|
{
|
|
m_edit_Extention.SetWindowText(strExt);
|
|
m_edit_ContentType.SetWindowText(strMime);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_edit_Extention.SetWindowText(_T(""));
|
|
m_edit_ContentType.SetWindowText(_T(""));
|
|
}
|
|
|
|
SetControlStates();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CMimeDlg::OnOK()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
'OK' button was pressed -- store data.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if no focus is to be set automatically, FALSE if the focus
|
|
is already set.
|
|
|
|
--*/
|
|
{
|
|
if (m_fDirty)
|
|
{
|
|
FillFromListBox();
|
|
}
|
|
|
|
CDialog::OnOK();
|
|
}
|