Заметки, идеи и мысли автора, обзор кода, алгоритмов, инструментов.

среда, 30 декабря 2009 г.

Узнаем о дате максимум. Модуль для работы с датой

Постольку поскольку моя профессиональная деятельность в большинстве случаев протекает именно в среде разработки Borland/CodeGear Delphi, то и рассматриваемые примеры в блогах будут приводиться соответствующие.

Давно, очень давно я увлекся написанием одной программки, которая была призвана помочь мне не забыть о важной дате, о Дне Рождении друга и т.п. Есть несколько версий программки, которые даже распространялись через инет, но не в том суть. Когда я начинал писать ту программку, первое с чем мне пришлось столкнуться - Даты.

Думаю не имеет смысла сейчас описывать тип TDateTime, кому интересно может заглянуть в запылившуюся на полке книжку либо воспользоваться Гуглом. А я тем временем, предлагаю рассмотреть модуль, который состоит из нескольких простейших казалось бы функций и процедур. Тем не менее некоторые из них придумались не за 5 и не за 10 минут. А отлаживались и того дольше. Но тем не менее прошли обкатку в реальных условиях использования и даже методом рефакторинга приведены к вполне разумному виду для понимания.

Итак привожу код модуля:
////////////////////////////////////////////////////////////////////////////////
//             ПРОЦЕДУРЫ И ФУНЦКЦИИ ДЛЯ РАБОТЫ С ДАТОЙ И ВРЕМЕНЕМ             //
//                АВТОР: МУКОМЕЛО ЕВГЕНИЙ БОГДАНОВИЧ ака XIO                  //
//                   Дата создания:  09072009                                 //
//                   Дата изменения: 25072009                                 //
////////////////////////////////////////////////////////////////////////////////
unit xDateWork;

interface

  Const
    tbdTypeDay    = 0;
    tbdTypeHour   = 1;
    tbdTypeMinute = 2;
    tbdTypeSecond = 3;

    Function GetAgeOf(XDate: TDateTime): integer;
    Function GetDaysCountOf(XDate: TDateTime): integer;
    Function GetJubiOf(XDate: TDateTime): Byte;
    Function GetZadiak(HB_Date_L:TDateTime):integer;
    Function TimeBeforeDate(DateX: TDateTime; ResType: Byte): Integer;

implementation

Uses DateUtils, SysUtils;

////////////////////////////////////////////////////////////////////////////////
//
//  ПРОЦЕДУРА: GetDateInfo
//
//  НАЗНАЧЕНИЕ: Выдает количество дней оставшихся до Дня Рождения, и возраст
//              человека.
//
//  ПАРАМЕТРЫ:
//    HB_DATE - Дата Рождения человека.
//    AGE - Возраст человека.
//    Days_Before_HB - Количество дней оставшихся до Дня Рождения.
//    Jubilee - Будет юбилейная дата или нет. 1/0
//
Procedure GetDateInfo(HB_DATE:TDateTime; var AGE:Integer; var Days_Before_HB:Integer; var Jubilee:Byte);
  Var
    Year_HB:    TDateTime; //Год рождения
    Year_Now:   TDateTime; //Текущий год
    CountYear:  TDateTime; //Количество лет
    Now_Date:   TDateTime; //Текущая дата

    CountYearI : Double;   //тоже самое по сути.. может без неё обойдемся???

    InkHB_Have: byte; //Индикатор того что Д.Р. в этом году уже был.  (0-не прошёл, 1-прошёл)
    StrDate: String[20];  //Дата рождения
    StrDate2: String[20]; //Текущая дата
    StrYear: String[20];  //текущий год
    StrYear2: String[20]; //текущий год + 1
    Ubl1: Integer; // возраст в формате Integer.
    Ubl2: String[20]; //для перевода Double в String и затем его в Integer...
    IntYear: integer; //переводим год в целое число что бы инкриментировать его.
    I: integer; // счетчик цикла.
    DTDate: TDateTime; //для преобразования строки в дату.
    CountDays1: Integer; //Количество дней до Нового года если ДР будет в след. году.
    CountDays2: Integer; //количество дней от начала года до ДР
    SumCount : Integer; //сумма количества дней, то есть количество дней оставшихся до ДР.
begin
  //Задаем начальные значения.
  Now_Date := DateOf(Now());
  Year_HB := YearOf(HB_Date);
  Year_Now := YearOf(Now());
  CountYear := Year_Now - Year_HB;
  CountYearI := CountYear;

  //Если месяц д. рождения > текущего
  //Тогда возраст в этом году ещё не прибавился на 1
  If MonthOf(HB_DATE) > MonthOf(Now_Date) then
    begin
      CountYearI := CountYearI - 1;
      InkHB_Have := 0
    end //если месяц др = текущему и день рождения больше текущего числа тогда в этом году др ещё не было.
  else If (MonthOf(HB_DATE) = MonthOf(Now_Date)) and (DayOf(HB_DATE) > DayOf(Now_Date)) then
        begin
          CountYearI := CountYearI - 1;
          InkHB_Have := 0
        end
       else //иначе ДР уже произошел
        InkHB_Have := 1;

//  AGE := CountYearI; // текущий возможный возраст.
  Ubl2 := FloatToStr(CountYearI);//переведем вещественное число в строку.
  Ubl1 := StrToInt(Ubl2); //строковое вещественное в целое.
  AGE := Ubl1; // текущий возможный возраст.
  StrDate  := DateToStr(HB_Date);
  StrDate2 := DateToStr(Now_Date);
  StrYear  := '';

  //если День Варенья ещё не произошел в этом году
  If InkHB_Have = 0 then
    begin  // у даты рождения подменяем год на текущий. для использования функции DaysBetween
      For I:=10 DownTo 7 do
        Delete(StrDate,I,1);
      For I:=7 to 10 do
        StrDate := StrDate + StrDate2[I];
      DTDate := StrToDate(StrDate);
      Days_Before_HB := DaysBetween(Now_Date,DTDate);
    end
  else
    begin
      For I:=7 to 10 do
        StrYear := StrYear + StrDate2[I]; //Записываем год текущий в виде строки.
      IntYear := StrToInt(StrYear); //Переводим текущий год в целое число.
      Inc(IntYear); //Инкрементируем.
      StrYear2 := IntToStr(IntYear); //Снова переводим в строку. уже инкрементированный вариант.
      For I:=10 DownTo 7 do
        Delete(StrDate,I,1);
      For I:= 1 to 4 do //в дате рождения подменяем год текущим годом.
        StrDate := StrDate + StrYear[I];
      StrDate2 := DateToStr(HB_DATE);// переводим значение Даты Рождения в формат строки.
      For I:=10 DownTo 7 do //Подменяем в дате Рождения год на текущий + 1
        Delete(StrDate2,I,1);
      For I:= 1 to 4 do
        StrDate2 := StrDate2 + StrYear2[I];
      DTDate := StrToDate(StrDate);

      //Далее обходим возможные ошибки связанные с тем, в каком году мы находимся...
      // Получаем количество дней до Нового Года
      CountDays1 := DaysBetween(Now_Date, StrToDate('31.12.'+StrYear));
      Try
        //пытаемся получить количество дней до ДР от начала следующего года...
        CountDays2 := DaysBetween(StrToDate('01.01.'+StrYear2), StrToDate(StrDate2));
      Except
        //если год не высокостный обрабатываем исключение связанное с ДР 29 Февраля.
        //И все же получаем количетсво дней до ДР.
        StrDate2[2] := '8';
        CountDays2 := DaysBetween(StrToDate('01.01.'+StrYear2), StrToDate(StrDate2));
      End;
      SumCount := CountDays1 + CountDays2; // получаем сумму количества дней оставшихся до ДР
      Days_Before_HB := CountDays1+CountDays2;
    end;

  If Now_Date = DTDate then //ДР наступил уже сегодня.
     Days_Before_HB := 0; //и дней осталось нуль.
  if Days_Before_HB = 0 then //раз дней осталось нуль значит ДР уже наступил или прошёл и возраст правильный.
    If (((Ubl1) mod 5) = 0)or(Ubl1 = 16)or(Ubl1 = 18) then
      Jubilee := 1
    else
      Jubilee := 0
  else //иначе ДР ещё только будет, и надо прибавить 1 чтобы понять будет Юбилей или просто ДР.
    If (((Ubl1+1) mod 5) = 0)or(Ubl1+1 = 16)or(Ubl1+1 = 18) then
      Jubilee := 1
    else
      Jubilee := 0;
end;

////////////////////////////////////////////////////////////////////////////////
//
//  ФУНКЦИЯ: GetAgeOf
//
//  НАЗНАЧЕНИЕ: Возвращает только возраст по дате рождения.
//
//  ПАРАМЕТРЫ:
//    XDate - Дата рождения, возраст от которой нужно расчитать.
//
Function GetAgeOf(XDate: TDateTime): integer;
var
  Age_L: Integer;
  Days_L: Integer;
  Jubi: Byte;
begin
  GetDateInfo(XDate,Age_L,Days_L,Jubi);
  GetAgeOf := Age_L;
end;

////////////////////////////////////////////////////////////////////////////////
//
//  ФУНКЦИЯ: GetDaysCountOf
//
//  НАЗНАЧЕНИЕ: Возвращает только количество дней до даты рождения.
//
//  ПАРАМЕТРЫ:
//    XDate - Дата рождения, до которой считаем колличество оставшихся дней.
//
Function GetDaysCountOf(XDate: TDateTime): integer;
var
  Age_L: Integer;
  Days_L: Integer;
  Jubi: Byte;
begin
  GetDateInfo(XDate,Age_L,Days_L,Jubi);
  GetDaysCountOf := Days_L;
end;

////////////////////////////////////////////////////////////////////////////////
//
//  ФУНКЦИЯ: GetJubiOf
//
//  НАЗНАЧЕНИЕ: Определяет, будет юбилей в этом году или нет.
//
//  ПАРАМЕТРЫ:
//    XDate - Дата рождения которую проверяем на юбилейность.
//
Function GetJubiOf(XDate: TDateTime): Byte;
var
  Age_L: Integer;
  Days_L: Integer;
  Jubi: Byte;
begin
  GetDateInfo(XDate,Age_L,Days_L,Jubi);
  GetJubiOf := Round(Jubi);
end;

////////////////////////////////////////////////////////////////////////////////
//
//  ФУНКЦИЯ: GetZadiak
//
//  НАЗНАЧЕНИЕ: По дате рождения, определяет знак задиака.
//
//  ПАРАМЕТРЫ:
//    HB_Date_L - Дата рождения, по которой будет определён знак задиака.
//
Function GetZadiak(HB_Date_L:TDateTime):integer;
Var
  Month: Integer;
  Day: Integer;
begin
  Try
    Month := MonthOf(HB_Date_L);
    Day := DayOf(HB_Date_L);
    If (Month=3) and (Day>=21) or (Month=4) and (Day<=20) then
      GetZadiak := 1; //Овен
    If (Month=4) and (Day>=21) or (Month=5) and (Day<=21) then
      GetZadiak := 2; //Телец
    If (Month=5) and (Day>=22) or (Month=6) and (Day<=21) then
      GetZadiak := 3; //Близнецы
    If (Month=6) and (Day>=22) or (Month=7) and (Day<=22) then
      GetZadiak := 4; //Рак
    If (Month=7) and (Day>=23) or (Month=8) and (Day<=23) then
      GetZadiak := 5; //Лев
    If (Month=8) and (Day>=24) or (Month=9) and (Day<=23) then
      GetZadiak := 6; //Дева
    If (Month=9) and (Day>=24) or (Month=10) and (Day<=23) then
      GetZadiak := 7; //Весы
    If (Month=10) and (Day>=24) or (Month=11) and (Day<=22) then
      GetZadiak := 8; //Скорпион
    If (Month=11) and (Day>=23) or (Month=12) and (Day<=21) then
      GetZadiak := 9; //Стрелец
    If (Month=12) and (Day>=22) or (Month=1) and (Day<=20) then
      GetZadiak := 10; //Козерог
    If (Month=1) and (Day>=21) or (Month=2) and (Day<=18) then
      GetZadiak := 11; //Водолей
    If (Month=2) and (Day>=19) or (Month=3) and (Day<=20) then
      GetZadiak := 12; //Рыбы
  Except
    GetZadiak := 0;
  end;
end;

////////////////////////////////////////////////////////////////////////////////
//
//  ФУНКЦИЯ: TimeBeforeDate
//
//  НАЗНАЧЕНИЕ: Как результат выдает количество оставшихся дней, часов, минут,
//              секунд до указанной даты.
//
//  ПАРАМЕТРЫ:
//    DateX   - Дата количество дней до которой необходимо расчитать
//    ResType - Тип возвращаемого результата (Дней, Часов, Минут, Секунд)
//
Function TimeBeforeDate(DateX: TDateTime; ResType: Byte): Integer;
Var
  DaysX:   Integer;
  HourX:   Integer;
  MinutX:  Integer;
  SecondX: Integer;
begin
  DaysX   := DaysBetween(Now,VarToDateTime(DateX));
  HourX   := HoursBetween(Now,VarToDateTime(DateX));
  HourX   := HourX - (DaysX * 24);
  MinutX  := MinutesBetween(Now,VarToDateTime(DateX));
  MinutX  := MinutX - (HoursBetween(Now,VarToDateTime(DateX)) * 60) + 1;
  SecondX := SecondsBetween(Now,VarToDateTime(DateX));
  SecondX := SecondX - (MinutesBetween(Now,VarToDateTime(DateX)) * 60) + 1;
  case ResType of
    tbdTypeDay:  Result := DaysX;
    tbdTypeHour: Result := HourX;
    tbdTypeMinute: Result := MinutX;
    tbdTypeSecond: Result := SecondX;
  end;
end;

end.


Первая процедура определяющая информацию о дате была именно GetDateInfo. С неё все начиналось. Именно она много раз переписывалась и долго отлаживалась на заре моих первых опытов программинга. В данный момент она совершенно рабочая, но тем не менее если кто-то заменит не рациональности или "баги" очень надеюсь что вы сообщите мне об этом. Так же буду рад увидеть более оптимальное решение задач которые решаются данной процедурой.
Функции: GetAgeOf, GetDaysCountOf, GetJubiOf были добавлены уже позднее в данном модуле, специально для уменьшения количества лишнего и повторяющегося кода в приложениях.

Ну и как понятно из комментариев в коде и названий функций,
GetAgeOf - возвращает возраст человека, чей день рождения в формате TDateTime, будет передан в функцию в качестве параметра.
GetDaysCountOf - в качестве результата выдает количество дней оставшихся до дня рождения. Дата рождения так же передается в качестве параметра.
GetJubiOf - может сказать, будет в этом году просто День Рождения либо будет Юбилей. Если Юбилей значит возвращаемое значение равно 1, в противном случае 0.

Далее в модуле можно увидеть функцию возвращающую знак зодиака. Ничего хитрого в ней нет. А написал я её и воспользовался ею так как являюсь старым "вентилятором" интернет-пейджера ICQ. И глядя на Аську я решил в своей той программке реализовать похожую фичу, определять по дате и выводить на экран знак зодиака человека, который записан в базе.

Ну и наконец последняя функция, идея не нова. Множество сайтов видел на которых шёл отсчет времени то до запуска БАКа (Большого Адронного Коллайдера), то до конца света... Ну, а мне как-то раз захотелось подсчитать сколько времени у меня осталось до поездки в город Костанай. Так и написал функцию TimeBeforeDate, которая возвращает, сколько дней, часов, минут и секунд осталось до указанной даты. Работает тоже довольно просто. У кого есть не растраченный энтузиазм, может немного дописать функцию что бы она подсчитывала так же и недели, месяцы, годы, веки =)

Повторюсь, если кто-то найдет в этих функциях "косяк"/"баг", либо захочет оптимизировать работу, либо более подробно распросить как это все работает, пишите, буду рад диалогу.

Теперь прежде чем привести показать небольшой примерчик как это все можно использовать, я покажу код ещё одной функции, которая будет очень полезна для работы с выше описанным модулем. Назначение этой функции состоит в том, что бы грамотно оформить интерфейс, красиво предоставить вычисленную информацию. Итак, код:


////////////////////////////////////////////////////////////////////////////////
//
//  ФУНКЦИЯ: NumericalWords
//
//  НАЗНАЧЕНИЕ: В зависимости от числа возвращает слово в правильном падеже.
//
//  ПАРАМЕТРЫ:
//    Word1 - Слово в Именительном падеже.
//    Word2 - Слово в Родительном падеже.
//    Word3 - Слово во множественом числе и Именительном падеже.
//    Num - Число по которому будет определен правильный падеж и число.
//
Function NumericalWords(Word1,Word2,Word3:String;Num:integer): String;
Var
  NumStr: String[32];
  LenStr,TestNum: Integer;
begin
  case Num of
          1: NumericalWords := Word1;
       2..4: NumericalWords := Word2;
    5..20,0: NumericalWords := Word3;
  else
    begin
      NumStr := IntToStr(Num);
      LenStr := Length(NumStr);
      if LenStr > 1 then
        Try
          TestNum := StrToInt(NumStr[LenStr-1]);
        Except
        End;
      if TestNum > 1 then
        Try
          Delete(NumStr,1,LenStr-1);
        Except
        End
      else
        Try
          Delete(NumStr,1,LenStr-2);
        Except
        End;
      Num := StrToInt(NumStr);
      case Num of
              1: NumericalWords :=  Word1;
           2..4: NumericalWords :=  Word2;
        5..20,0: NumericalWords :=  Word3;
      end;
    end;
  end;
end;

Всем известно что некрасиво будет написать "4 день" или "1 дней". То есть чисто с технической точки зрения это понятная форма представления информации. Но всегда и во всем должна присутствовать красота, рациональность. И вот как раз для того что бы выводимые пользователю строки не выводились как в выше описанных примерах, можно воспользоваться функцией NumericalWords. Думаю если погуглить в интернете найдется множество вариантов написания такой функции, но я предлагаю свой, возможно и не самый рациональный метод решения задачи.

А теперь на засыпку приведу пример, как это все можно использовать. Допустим у Вас есть таблица с данными. В таблице забиты фамилии, даты рождения. А помимо этого нужно расчитать сколько лет, юбилей ни юбилей и сколько дней до Дня Рождения осталось. В таком случае у нужного TDataSet создадим 3 вычисляемых поля. А в событии OnCalcFields напишем примерно следующий код:

procedure TdmMainData.dstSubjectCalcFields(DataSet: TDataSet);
Var
  Age: Integer;
  DaysCount: Integer;
  SignI: Integer;
  Juby: Integer;
  SingnStrM: array [0..12] of String;
begin
  SingnStrM[0] := '';
  SingnStrM[1] := 'Овен';
  SingnStrM[2] := 'Телец';
  SingnStrM[3] := 'Близнецы';
  SingnStrM[4] := 'Рак';
  SingnStrM[5] := 'Лев';
  SingnStrM[6] := 'Дева';
  SingnStrM[7] := 'Весы';
  SingnStrM[8] := 'Скорпион';
  SingnStrM[9] := 'Стрелец';
  SingnStrM[10] := 'Козерог';
  SingnStrM[11] := 'Водолей';
  SingnStrM[12] := 'Рыбы';

  if dstSubjectDate1.Value <> 0 then
    begin
      Age := GetAgeOf(dstSubjectDate1.Value);
      DaysCount := GetDaysCountOf(dstSubjectDate1.Value);
      SignI := GetZadiak(dstSubjectDate1.Value);
      dstSubjectclAge.Value := IntToStr(Age) + NumericalWords(' год',' года',' лет',Age);
      dstSubjectclBeforeHB.Value := IntToStr(DaysCount) + NumericalWords(' день',' дня',' дней',DaysCount);
      dstSubjectclSign.Value := SingnStrM[SignI];
    end
  else
    begin
      dstSubjectclAge.Value := '';
      dstSubjectclBeforeHB.Value := '';
      dstSubjectclSign.Value := '';
    end;

  Juby := GetJubiOf(dstSubjectDate1.Value);

  // Отобразим иконки в гриде. Соответсвенно юбилею, просто ДР или ничего..
  If (DaysCount <= 3) and (Juby = 0) then
    dstSubjectclIcon.Value := 1
  else if (DaysCount <= 3) and (Juby = 1) then
    dstSubjectclIcon.Value := 2
  else
    dstSubjectclIcon.Value := 0;
end;

В данном примере так же показано что помимо прочего в гриде можно добавить колонку в которой в зависимости от близости празднества будет отображаться соответствующая иконка. Например красный надувной шарик, как в той же Аське.

По хорошему, конечно, то что описано выше - делать НЕЛЬЗЯ! Почему спросите Вы? Да потому что, вам может быть и не понадобиться все выведенные данные. А если в базе записей более тысячи? Компьютеру придется попыхтеть. Пользователь конечно можно быть и не заметит колоссальной работы машины. Особенно учитывая современные мощности. Тем не менее подход является не оптимальным, и в идеале такого рода задачу нужно переносить на СУБД. Что в принципе я думаю реально. И намериваюсь заняться этой задачей в свободное время. Ну а пока, рекомендую выше изложенный материал использовать более гуманно по отношению к вашему ЦП. Например, под Гридом с данными можно "нарисовать" поля, и при переходе на новую запись в базе, проводить подсчеты и выводить данные в эти поля (лэйблы). В результате у Вас должно получиться что-то вроде этого:






Но это лишь один вариант, а их очень много, так что включаем фантазию и творим.

Думаю это пока все. В завершение с позволения сказать статьи, скажу так: пишите код, оптимизируйте, найдя баги печатайте его и скомкав лист бумаги бросайте его в камин.
Не все то оптимально, что складно написано, не все то рационально что легко получается. Все гениальное - просто...

В общем если у вас есть интересные мысли, замечания или идеи на данную тему, пишите, буду рад.

четверг, 22 октября 2009 г.

Мой первый блог.

Для формальности хочу сообщить что мой первый блог расположен тут.
А здесь я только осваиваюсь. Но все ещё в переди :)

XML+XSL Универсальная пара

Разумеется пост рассчитан на тех кто хоть как то связан с IT и хотя бы близок к программированию.

Предлагаю рассмотреть такую универсальную пару как XML+XSL.

Думаю не стоит здесь подробно расписывать о том что такое XML. Многие о нем слышали, работали с ним, или как минимум видели как он выглядит в текстовом редакторе или ВэбБраузере.

XSL же представляет собой смесь HTML и XML, плюс встроенные функции и много возможностей для расширения, если выражаться простым языком.

Использование данной связки позволяет нам динамически, без сторонних программ, придавать данным из XML файлов отформатированный и презентабельный внешний вид, удобный для просмотра, чтения.

Так же в обзор можно включить возможность MS SQL начиная с 2005й версии формировать из данных, выбранные "селектом" данные в формат XML.

Теперь попробую более внятно изложить суть прелести. В наше время существует множество программ, множество операционных систем. Но и там и тут в большинстве случаев используются базы данных. Там где есть базы данных, есть XML. Экспорт как минимум. XML вообще часто используется для обмена данными между разным ПО. Данную возможность можно взять на вооружение и использовать скажем, для формирования отчетов. Грубо говоря, имея под рукой Текстовый редактор, MS SQL 2005 или выше, я уже могу формировать отчеты самой разнообразной внешности, при этом буду иметь возможность выкладывать эти отчеты в интернете в виде HTML страниц, сохранять эти отчеты в формате MS WORD или EXCEL. Так же можно пользоваться офисными программами OpenOffice. Данные отчеты можно открыть и посмотреть как на Windows так и на Linux, главное условие - наличие Вэб Браузера, парсера XML и встроенных в систему библиотек XSL. А поскольку это стандарт, библиотеки я думаю встроены в большинстве современных ОС.

Таким образом на мой взгляд и взгляд моих коллег с которыми мы вместе разбирались с граблями XSL'я, это очень удобный, надежный и универсальный инструмент, который можно использовать в любом ПО, на любой платформе.
Кроме того, данное средство позволяет без базы данных, имея лишь XML файл, делать сортировку и фильтрацию данных.

Далее хочу привести небольшой примерчик использования XML+XSL+SQL.

Допустим есть у нас табличка в базе данных. Попробуем "вытащить" из неё данные в формате XML.

Select * From Cars order by Car
for xml raw('Item'), elements,type

Такой запрос выдаст нам в качестве результата XML строку следующего вида:
<Item>
    <Car Country="Japan">TOYOTA</Car>
    <Desc>Corolla, Camry, FJ Cruser</Desc>
  </Item>
  <Item>
    <Car Country="Japan">HONDA</Car>
    <Desc>Civic, Accord, CR-V</Desc>
  </Item>
  <Item>
    <Car Country="Gemany">BMW</Car>
  </Item>
  <Item>
    <Car Country="USSR">LADA 2101</Car>
  </Item>

Далее в коде программы, либо руками мы можем к этим данным добавить какие-то дополнительные тэги, дополнительную информацию, ну и разумеется привести к нормальному виду XML. В результате получим что-то вроде этого:
<?xml version="1.0" encoding="WINDOWS-1251"?>
<?xml-stylesheet type='text/xsl' href= 'XStyle.xsl' ?>
<XML>
<Comment>В данной таблице информация по основным маркам Авто</Comment>
<Cars>
  <Item>
    <Car Country="Japan">TOYOTA</Car>
    <Desc>Corolla, Camry, FJ Cruser</Desc>
  </Item>
  <Item>
    <Car Country="Japan">HONDA</Car>
    <Desc>Civic, Accord, CR-V</Desc>
  </Item>
  <Item>
    <Car Country="Gemany">BMW</Car>
  </Item>
  <Item>
    <Car Country="USSR">LADA 2101</Car>
  </Item>
</Cars>
<Date>01.01.2009</Date>
<Amount>20999955412.12</Amount>
</XML>
Заметьте, в самом начале файла я добавил строку

<?xml-stylesheet type='text/xsl' href= 'XStyle.xsl' ?>
Она означает что для открытия данного файла через вэб браузер будет использоваться файл шаблонов вида XSL. И указана ссылка на файл шаблона. В данном случае браузер будет искать файл в том каталоге в котором находится файл с данными XML. В некоторых случаях будет целесообразно указать полный путь к файлу шаблону. Допустим если он будет располагаться в директории в которую установлена ваша программа.

Теперь нам нужно подготовить файл шаблона, который будет оформлять данные так как нам необходимо. Для каждой структуры XML файлов нужно писать персональный шаблон, разумеется. Но для одинаковой структуры можно использовать один и тот же шаблон.
Вот мой пример XSL шаблона в котором я постарался задействовать наиболее часто используемые возможности/функции:
<?xml version="1.0" encoding="WINDOWS-1251" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt">
  <xsl:decimal-format name="european" decimal-separator=',' grouping-separator='.' />
  <xsl:template match="/">

<!--Объявления Стилей********************************************************-->
    <STYLE>
      .Headers
      {
        font-family: Times New Roman;
        font-size: 22;
        font-weight: bold;
        font-color: "#009999";     
        font-style: normal;
      }
      .SysColonTitles
      {
        font-family: Times New Roman;
        font-size: 12;
        font-weight: normal;
        font-color: "#2F2F2F";     
        font-style: normal;
        Color: "#2F2F2F";     
      }
      .OutData1
      {
        font-family: Times New Roman;
        font-size: 18;
        font-weight: normal;
        color: "black";     
        font-style: normal;
      }
      .OutData2
      {
        font-family: Times New Roman;
        font-size: 18;
        font-style: italic;
        font-weight: normal;
        color: "#CC0000";      
      }
    </STYLE>

<!--Заголовок о том где был сформирован отчет...*****************************-->
  <Div align='RIGHT' class="SysColonTitles">
    Сформировано c помощью стилев XSL
  </Div>
 
<!--Заголовок****************************************************************-->
  <Div align='Center' CLASS="Headers">
    Справка по Машинам
  </Div>
 
<!--Выходные данные**********************************************************-->
  <Div align='Left' CLASS="Headers">
    Выходные данные:
  </Div>
 
  <xsl:if test="XML/Comment">
    <Div align='Left' CLASS="OutData2">
      Коментарий: <xsl:value-of select="//Comment"/>
    </Div>
  </xsl:if>
  <xsl:if test="not(XML/Comment)">
    <Div align='Left' CLASS="OutData2">
      Коментарий: Отсутсвует...
    </Div>
  </xsl:if> 
   
  <table border="1">
  <tr>
  <td>
    <strong>Страна происхождения</strong>     
  </td>
  <td>
    <strong>Марка</strong>     
  </td>
  <td>
    <strong>Модели</strong>     
  </td>
  </tr>
    <xsl:for-each select="XML/Cars/Item">
      <tr CLASS="OutData1">
        <td>
          <xsl:value-of select="Car/@Country"/>
        </td>
        <td>
          <xsl:value-of select="Car"/>
        </td>
        <xsl:if test="//Cars/Item/Desc">
          <td>
            <xsl:value-of select="Desc"/>
          </td>
        </xsl:if>
      </tr>
    </xsl:for-each>
  </table>

  <Div align='Left' CLASS="OutData1">
  <BR/>
    Сумма денег: <xsl:value-of select="format-number(//Amount,'###.###,00','european')"/>
  </Div>
  </xsl:template>
</xsl:stylesheet>

Теперь когда мы откроем с помощью вэб браузера наш XML файл, мы получим примерно следующий вид:



В общем-то, в данном описание приведены основные ключевые моменты. Если вдруг у кого-то возникнут вопросы вы можете воспользоваться Гуглом, либо можете спросить в блоге.

Одна из граблей на которую недавно наступил:
Использую цикл, я использовал для вывода данных в таблицу полный путь вложенности. От самого Roor'а, в итоге в каждой строке выводилась первая строка, так как данные вытаскивались из первого встреченного в файле тэга. В цикле необходимо использовать наименования тэгов опуская имя тэга в который вложен Item.

понедельник, 10 августа 2009 г.

Hello World

Первая запись как водиться является чем-то простейшим!
Да будет Блог, сказал XIO и запостил этот пост! :)

Постоянные читатели