Перенесення VBA-макросів в Delphi (исходники), Різне, Програмування, статті

Запис макросу (меню Excel “СервісМакросНачать запис …”) незамінна річ при написанні звітів або створення діаграм в Excel “е, особливо для тих, хто тільки починає з ним працювати. Але, записаний в Excel макрос, іноді виглядає досить громіздко і читається з працею. У даній статті я хочу розглянути методи перекладу записаних макросів в більш зручний вид для використання їх в Delphi. Також буде розглянуто деякі нестиковки в об’єктної моделі Excel “я в записаних макросах і методи їх виправлення.


Для початку розглянемо записані в Excel “е макроси і спробуємо скоротити їх VBA-код для переносу в Delphi. Відкриємо в Excel” e нову книгу і виконаємо, наприклад, прості дії – запустимо запис макросу, виділимо область “A1: D5” і в тулбарі “Межі” виберемо “Все кордону”. Зупинимо запис макросу і подивимося, що у нас вийшло. Повинен з’явитися приблизно такий код (щоб відкрити VBA редактор в Excel “е натисніть Alt + F11):







Sub Макрос1 ()

Range(“A1:D5”).Select
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
Selection.Borders(xlDiagonalUp).LineStyle = xlNone
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = xlAutomatic
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = xlAutomatic
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = xlAutomatic
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = xlAutomatic
End With
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = xlAutomatic
End With
With Selection.Borders(xlInsideHorizontal)
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = xlAutomatic
End With
End Sub
Так, забагато … Давайте подивимося, що містить отриманий VBA-код:

Тепер спробуємо скоротити цей макрос, наприклад, так (скопіюйте код, наведений нижче в VBA редактор):






Sub Макрос1_1 ()

With Range(“A1:D5”).Borders
.LineStyle = xlContinuous
.Weight = xlThin
.ColorIndex = xlAutomatic
End With
End Sub
Очистимо область “A1: D5” від кордонів і запустимо наш макрос (перейдіть в Excel з редактора натисніть Alt + F8, виберіть Макрос1_1 і натисніть “Виконати”). Код набагато коротше, а результат той же! Що ми зробили? По-перше, прибрали Select, просто вказавши яку область ми будемо “обордюрівать”, по-друге, взагалі не вказали які кордони будемо заповнювати, просто написавши Borders без параметрів (тобто все). Чому знадобилося прибирати Select? Тому що, по-перше, можна обійтися без нього, а по-друге, Select викликає доп. перемальовування екрану, а це, як відомо, самі довгі операції.


Тепер перейдемо до іншої “особливості” записи макросу, а саме до незрозумілого властивості об’єкта [Excel.] Application Selection. Що це таке? В даному макросі, як можна здогадатися це область осередків (Range). Давайте запишемо ще один макрос: додамо вікно інструментів “Малювання”, включимо запис макросу, виберемо тулбар “Напис”, помістимо її на наш лист і наберемо текст “Наша напис”. Виділимо комірку A1 і зупинимо запис макросу. Повинен вийти приблизно такий код:







Sub Макрос2 ()

ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, 19.5, 88.5, _
191.25, 86.25).Select Selection.Characters.Text = “Наша напис”
With Selection.Characters(Start:=1, Length:=7).Font
.Name = “Arial” . FontStyle = “звичайний”
.Size = 10
.Strikethrough = False
.Superscript = False
.Subscript = False
.OutlineFont = False
.Shadow = False
.Underline = xlUnderlineStyleNone
.ColorIndex = xlAutomatic
End With
Range(“A1”).Select
End Sub
Знову спробуємо скоротити код:






Sub Макрос2_2 ()
Dim MyShape As Shape
Set MyShape = ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, _
19.5, 88.5, 191.25, 86.25)
MyShape.Characters.Text = “Наша напис”
End Sub
Перейдемо в Excel, видалимо нашу напис і виконаємо макрос Макрос2_2. Отримаємо помилку “Об’єкт не підтримує дане властивість або метод” на рядку з кодом





MyShape.Characters.Text = “Наша напис”. 
Чому Selection його підтримує, а Shape ні? Подивившись на об’єкт Shape ми не знайдемо властивості Characters. Що ж ховається за загадковим Selection? Для того щоб це зрозуміти давайте в Макрос2, додамо рядок MsgBox TypeName (Selection) після рядка





Selection.Characters.Text = “Наша напис”
і виконаємо макрос. Отримаємо повідомлення “TextBox”.

Ось воно що! Значить Selection – це TextBox. Спробуємо створити такий об’єкт і … Немає такого об’єкта! Є тільки TextFrame. Заміна Shape на TextFrame теж не увінчається успіхом … Що ж робити?


Подивимося на властивості об’єкта Shape і побачимо там властивість TextFrame, у якого вже є властивість Characters … Подивившись довідку по VBA можна переконатися, що Characters – це метод і належить об’єкту TextFrame. Пробуємо:







Sub Макрос2_2 ()

Dim MyShape As Shape
Set MyShape = ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, _
19.5, 88.5, 191.25, 86.25)
MyShape.TextFrame.Characters.Text = “Наша напис”
End Sub
Запустимо макрос – працює! Залишимо міфічний TextBox на совісті Microsoft …



Примітка:
об’єкт TextBox таки існує, але тільки як Control для Form.

Ще невеликий приклад на VBA про Selection і займемося безпосередньо переносом коду з VBA в Delphi. Відкрийте файл Кніга1.xls, який прикладений до статті і перейдіть на Аркуш2. Там таблиця і графік. Включимо запис макросу, виділимо перший стовпчик, викличемо “Формат рядів даних” і змінимо колір на темно синій. Зупинимо запис. Повинен вийти приблизно такий код:







Sub Макрос3 ()
” ActiveSheet.ChartObjects (“діагр. 1”). Activate
ActiveChart.SeriesCollection(1).Select
With Selection.Border
.Weight = xlThin
.LineStyle = xlAutomatic
End With
Selection.InvertIfNegative = False
With Selection.Interior
.ColorIndex = 23
.Pattern = xlSolid
End With
End Sub
Перевіримо, як він працює – перейдемо в Excel, викличемо макроси і запустимо Макрос3… Помилка на першому ж рядку! Записаний макрос не працює. Чому? Спробуємо зробити так, щоб він запрацював. Напишемо невеликий макрос (руками) і будемо вставляти в нього код і тестувати. Почнемо з визначення імен наявних на аркуші діаграм:







Sub Test1()
Dim i As Integer
For i = 1 To ActiveSheet.ChartObjects.Count
MsgBox ActiveSheet.ChartObjects(i).Name
Next i
End Sub
Запустивши макрос, отримаємо ім’я діаграми “Chart 1” – чому не “діагр. 1”, як в записаному макросі – це чергова загадка. Виправимо макрос і перевіримо:







Sub Макрос3 ()

ActiveSheet.ChartObjects(“Chart 1”).Activate
ActiveChart.SeriesCollection(1).Select
With Selection.Border
.Weight = xlThin
.LineStyle = xlAutomatic
End With
Selection.InvertIfNegative = False
With Selection.Interior
.ColorIndex = 23
.Pattern = xlSolid
End With
End Sub
Працює: o).

Далі визначимо тип об’єкта після рядка ActiveChart.SeriesCollection (1). Select відомої рядком MsgBox TypeName (Selection). Отримаємо Series. Скоротимо макрос і позбудемося Selection.







Sub Макрос3_3 ()

Dim ch As Chart, s As Series
Set ch = ActiveSheet.ChartObjects(“Chart 1”).Chart
Set s = ch.SeriesCollection(1)
With s.Interior
.ColorIndex = 23
.Pattern = xlSolid
End With
End Sub
Якщо подивитися на код Макрос3 і Макрос3_3, то видно, що код в Макрос3 використовує Selection як проміжний буфер для передачі управління між об’єктами, тобто Activate, Select і для “безликого” виклику властивостей і методів. Щоб отримати об’єкт типу Chart нам знадобилося додати звернення до властивості ChartObject.Chart







Set ch = ActiveSheet.ChartObjects(“Chart 1”).Chart
Далі ми просто поміняли колір стовпчика без використання Select.

Звичайно, це далеко не всі загадки при записі макросів – їх ще багато, але нам зараз потрібно було зрозуміти, що це можливо і як з цим боротися.


Перенесемо наш код в Delphi і паралельно в C # (якщо не заперечуєте).

Відразу обмовлюся, що в статті не розглядаються методи підключення до Excel “ю (з даного питання можна почитати тут ), Також використовується раннє зв’язування (що це таке читайте тут).


Я вважаю пізніше зв’язування не “паскалевкім” підходом, тому що скрізь використовується один тип Variant (як у мові “Основняк”), Що, по моєму, те саме що шаманізму – щось відбувається, щось кудись записується, але ніхто не розуміє, чому це працює.


Почнемо з Макрос1. Так, саме з нього, а не скороченого варіанту. Спробуємо написати код для перших трьох рядків:


Delphi






ASheet.Range[“A1:D5”, EmptyParam].Select;
XL.Selection[lcid].Borders[xlDiagonalDown].LineStyle := xlNone;
XL.Selection[lcid].Borders[xlDiagonalUp].LineStyle := xlNone;
Спробувавши скомпілювати дану ділянку, відразу ж отримаємо помилку компілятора “E2003 Undeclared identifier:” Borders “”. Подивимося, який тип має Selection (в даному прикладі дивимося файл Excel2000.pas):






property ExcelApplication.Selection[lcid: Integer]: IDispatch;
Подивившись на інтерфейс IDispatch, ми справді не знайдемо такої властивості і методу … Спробуємо підправити код:


Delphi






ASheet.Range[“A1:D5”, EmptyParam].Select;
(XL.Selection[lcid] as ExcelRange).Borders[xlDiagonalDown].LineStyle := xlNone;
(XL.Selection[lcid] as ExcelRange).Borders[xlDiagonalUp].LineStyle := xlNone;
with (XL.Selection[lcid] as ExcelRange).Borders[xlEdgeLeft] do begin
LineStyle := xlContinuous;
Weight := xlThin;
ColorIndex := xlAutomatic;
end;
with (XL.Selection[lcid] as ExcelRange).Borders[xlEdgeTop] do begin
LineStyle := xlContinuous;
Weight := xlThin;
ColorIndex := xlAutomatic;
end;
with (XL.Selection[lcid] as ExcelRange).Borders[xlEdgeBottom] do begin
LineStyle := xlContinuous;
Weight := xlThin;
ColorIndex := xlAutomatic;
end;
with (XL.Selection[lcid] as ExcelRange).Borders[xlEdgeRight] do begin
LineStyle := xlContinuous;
Weight := xlThin;
ColorIndex := xlAutomatic;
end;
C#






ASheet.get_Range(“A1:D5”, Type.Missing).Select();
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlDiagonalDown).LineStyle =
Excel.XlLineStyle.xlLineStyleNone;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlDiagonalUp).LineStyle =
Excel.XlLineStyle.xlLineStyleNone; / / Ліва межа
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeLeft).LineStyle =
Excel.XlLineStyle.xlContinuous;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeLeft).Weight =
Excel.XlBorderWeight.xlThin;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeLeft).ColorIndex =
Excel.XlColorIndex.xlColorIndexAutomatic; / / Верхня межа
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeTop).LineStyle =
Excel.XlLineStyle.xlContinuous;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeTop).Weight =
Excel.XlBorderWeight.xlThin;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeTop).ColorIndex =
Excel.XlColorIndex.xlColorIndexAutomatic; / / Нижня межа
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeBottom).LineStyle =
Excel.XlLineStyle.xlContinuous;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeBottom).Weight =
Excel.XlBorderWeight.xlThin;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeBottom).ColorIndex =
Excel.XlColorIndex.xlColorIndexAutomatic; / / Права кордон
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeRight).LineStyle =
Excel.XlLineStyle.xlContinuous;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeRight).Weight =
Excel.XlBorderWeight.xlThin;
((Excel.Range) XL.Selection).Borders.get_Item(
Excel.XlBordersIndex.xlEdgeRight).ColorIndex =
Excel.XlColorIndex.xlColorIndexAutomatic;
Працює … Що ми для цього зробили? Привели тип IDispatch до ExcelRange: XL.Selection [lcid] as ExcelRange). Але таке переведення записаного макросу в Delphi воістину героїчну працю, та й чи потрібен нам Select для того щоб намалювати кордону (а дивлячись на C # код, взагалі можна відразу відмовитися на ньому програмувати)? Адже всяка перемальовування – зайва трата часу і, отже, швидкості. Тому займемося Макросом1_1:


Delphi






with ASheet.Range[“A1:D5”, EmptyParam].Borders do begin
LineStyle := xlContinuous;
Weight := xlThin;
ColorIndex := xlAutomatic;
end;
C#






oRng = ASheet.get_Range(“A1:D5”, Type.Missing); / / Встановимо све кордону
oRng.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
oRng.Borders.Weight = Excel.XlBorderWeight.xlThin;
oRng.Borders.ColorIndex = Excel.XlColorIndex.xlColorIndexAutomatic;
Відмінності є? Ми не робили Select і не використовували безликий Selection, звернувшись безпосередньо до області ExcelRange. Чи все ж краще з Selection? Порівняйте:


Delphi






ASheet.Range[“A1:D5”, EmptyParam].Select;
with (XL.Selection[lcid] as ExcelRange).Borders do begin
LineStyle := xlContinuous;
Weight := xlThin;
ColorIndex := xlAutomatic;
end;
Все те ж саме, але щось рябить в очах при Select, чи не так? І начебто якось повільніше або мені здалося?


Перейдемо до Макрос2, вірніше до вже підготовленого Макрос2_2: Delphi






MyShape := (XL.ActiveWorkbook.ActiveSheet as _Worksheet).Shapes.AddTextbox(
msoTextOrientationHorizontal, 19.5, 88.5, 191.25, 86.25); MyShape.TextFrame.Characters (EmptyParam, EmptyParam). Text: = “Наша напис”;
C#






myShape = (Excel.Shape) ASheet.Shapes.AddTextbox(
Office.MsoTextOrientation.msoTextOrientationHorizontal,
(float) 19.5, (float) 88.5, (float) 191.25, (float) 86.25);
myShape.TextFrame.Characters(Type.Missing, Type.Missing).Text = “Наша напис”;
У коді на Delphi практично ніяких відмінностей, крім вказівки двох обов’язкових параметрів: почала змінюваних символів і їх довжини. Ми написали EmptyParam, тим самим вказавши, що обробляється весь текст.


І, нарешті, Макрос3_3. Ускладнимо його – повністю створимо таблицю з даними, створимо графік і змінимо колір першого стовпця:


Delphi






oSheet.Cells.Item[1, 1] := “First Name”;
oSheet.Cells.Item[1, 2] := “Last Name”;
oSheet.Cells.Item[1, 3] := “Full Name”;
oSheet.Cells.Item[1, 4] := “Salary”;
//Format A1:D1 as bold, vertical alignment := center.
oSheet.Range[“A1”, “D1”].Font.Bold := True;
oSheet.Range[“A1”, “D1”].VerticalAlignment := xlVAlignCenter;
// Create an array to multiple values at once.
saNames := VarArrayCreate([0, 4, 0, 1], varVariant);
saNames[0, 0] := “John”;
saNames[0, 1] := “Smith”;
saNames[1, 0] := “Tom”;
saNames[1, 1] := “Brown”;
saNames[2, 0] := “Sue”;
saNames[2, 1] := “Thomas”;
saNames[3, 0] := “Jane”;
saNames[3, 1] := “Jones”;
saNames[4, 0] := “Adam”;
saNames[4, 1] := “Johnson”;
oSheet.Range[“A2”, “B6”].Formula := saNames;
oRng := oSheet.Range[“C2”, “C6”];
oRng.Formula := “=A2 & ” ” & B2″;
oRng := oSheet.Range[“D2”, “D6”];
oRng.Formula := “=RAND()*100000”;
oSheet.Range[“A1”, “D1”].EntireColumn.AutoFit;
/ / Створимо графік на аркуші в облас E8: L29
Ch := (oSheet.ChartObjects as ChartObjects).Add(
oSheet.Range[“B8”, EmptyParam].Left,
oSheet.Range[“B8”, EmptyParam].Top,
oSheet.Range[“I8”, EmptyParam].Left – oSheet.Range[“B8”, EmptyParam].Left,
oSheet.Range[“B30”, EmptyParam].Top – oSheet.Range[“B8”, EmptyParam].Top).Chart
as _Chart;
oRng := oSheet.Range[“C1”, “D6”];
with Ch do begin
SetSourceData(oRng, xlRows);
ChartType := xl3DColumnClustered;
HasTitle[lcid] := True; ChartTitle [lcid]. Characters [EmptyParam, EmptyParam]. Text: = “Діаграма 1”;
(Axes(xlCategory, xlPrimary, lcid) as Axis).HasTitle := False;
(Axes(xlValue, xlPrimary, lcid) as Axis).HasTitle := True;
(Axes(xlValue, xlPrimary, lcid) as Axis).AxisTitle. Characters [EmptyParam, EmptyParam]. Text: = “Деньги”;
(Axes(xlValue, xlPrimary, lcid) as Axis).AxisTitle.Orientation := xlUpward;
end;
/ / Тут код заміни кольору у першого стовпчика / / Взятий з Макрос3_3
with (Ch.SeriesCollection(1, lcid) as Series) do begin
Interior.ColorIndex := 23;
Interior.Pattern := xlSolid;
end;
C#






oSheet.Cells[1, 1] = “First Name”;
oSheet.Cells[1, 2] = “Last Name”;
oSheet.Cells[1, 3] = “Full Name”;
oSheet.Cells[1, 4] = “Salary”;
//Format A1:D1 as bold, vertical alignment := center.
oSheet.get_Range(“A1”, “D1”).Font.Bold = true;
oSheet.get_Range(“A1”, “D1”).VerticalAlignment =
Excel.XlVAlign.xlVAlignCenter;
oSheet.get_Range(“A1”, “D1”).HorizontalAlignment =
Excel.XlHAlign.xlHAlignCenter;
// Create an array to multiple values at once.
string[,] saNames = new string[5, 2];
saNames[0, 0] = “John”;
saNames[0, 1] = “Smith”;
saNames[1, 0] = “Tom”;
saNames[1, 1] = “Brown”;
saNames[2, 0] = “Sue”;
saNames[2, 1] = “Thomas”;
saNames[3, 0] = “Jane”;
saNames[3, 1] = “Jones”;
saNames[4, 0] = “Adam”;
saNames[4, 1] = “Johnson”;
oSheet.get_Range(“A2”, “B6″).Formula = saNames;
//Fill C2:C6 with a relative formula (=A2 & ” ” & B2).
oRng = oSheet.get_Range(“C2”, “C6”);
oRng.Formula = “=A2 & ” ” & B2″;
//Fill D2:D6 with a formula(=RAND()*100000) and apply format.
oRng = oSheet.get_Range(“D2”, “D6”);
// oRng.Formula = “=RAND()*100000”; oRng.Formula = “= RAND () * 100000”;
// oRng.NumberFormat = “0.00”;
//AutoFit columns A:D.
oRng = oSheet.get_Range(“A1”, “D1”);
oRng.EntireColumn.AutoFit();
/ / Створимо графік на аркуші в облас E8: L29
Ch = ((Excel.ChartObjects) oSheet.ChartObjects(Type.Missing)).Add(
(double) oSheet.get_Range(“B8”, Type.Missing).Left,
(double) oSheet.get_Range(“B8”, Type.Missing).Top,
(double) oSheet.get_Range(“I8”, Type.Missing).Left –
(double) oSheet.get_Range(“B8”, Type.Missing).Left,
(double) oSheet.get_Range(“B30”, Type.Missing).Top –
(double) oSheet.get_Range(“B8”, Type.Missing).Top
).Chart;
oRng = oSheet.get_Range(“C1”, “D6”);
Ch.SetSourceData(oRng, Excel.XlRowCol.xlRows);
Ch.ChartType = Excel.XlChartType.xl3DColumnClustered;
Ch.HasTitle = true; Ch.ChartTitle.get_Characters (Type.Missing, Type.Missing). Text = “Діаграма 1”;
((Excel.Axis) Ch.Axes(Excel.XlAxisType.xlCategory,
Excel.XlAxisGroup.xlPrimary)).HasTitle = false;
((Excel.Axis) Ch.Axes(Excel.XlAxisType.xlValue,
Excel.XlAxisGroup.xlPrimary)).HasTitle = true;
((Excel.Axis) Ch.Axes(Excel.XlAxisType.xlValue,
Excel.XlAxisGroup.xlPrimary)).AxisTitle. get_Characters (Type.Missing, Type.Missing). Text = “Деньги”;
((Excel.Axis) Ch.Axes(Excel.XlAxisType.xlValue,
Excel.XlAxisGroup.xlPrimary)).AxisTitle.Orientation =
Excel.XlOrientation.xlUpward;
/ / Тут код заміни кольору у першого стовпчика / / Взятий з Макрос3_3
((Excel.Series) Ch.SeriesCollection(1)).Interior.ColorIndex = 23;
((Excel.Series) Ch.SeriesCollection(1)).Interior.Pattern =
Excel.XlPattern.xlPatternSolid;
З перенесених рядків з Макрос3_3 видно, що колекція Ch.SeriesCollection (1, lcid) теж повертає інтерфейс IDispatch, тому ми привели її до типу Series. Чому в бібліотеці типів відразу не використаний тип Series залишається тільки гадати. Ще в щойно описаному прикладі наведено код завдання титулів для осей (axes) і тут метаморфоза перетворення Axes в Axis, тобто Axes – це колекція Axis, хоча в VBA це ні як не відображається.


Резюме:


Ми розглянули кілька прикладів перекладу VBA коду, створеного записом макросу в Excel в Delphi. Побачили, як можна скоротити непотрібний код, позбувшись Select. Як уникнути безликого Selection (тип IDispatch) щоб уникнути помилок і можливих непорозумінь. Також виявили невідповідність записаного коду (наприклад, імені об’єкта “Наша напис”) і типів реальним типам об’єктів. Тобто записаний код VBA не завжди виявляється працездатним. Для правильного перекладу VBA в Delphi вимагається подання про об’єктної моделі Excel “я, звернення до довідки Excel VBA, а також велике бажання досягти результату.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*