середа, 26 червня 2013 р.

Підсвічування синтаксису HTML-тексту за допомогою регулярних виразів

Досить часто виникає потреба у автоматичному виділенні ключових слів при формуванні html-документу, наприклад текст SQL або модуля на певній мові програмування. Це досить просто зробити за допомогою регулярних виразів.
Для цього потрібно підключити в uses модуль RegularExpressions.

Шаблон (в даному випадку для PL/SQL) прописаний в константі C_LEXEM_PATTERN, змінити або доповнити його буде не складно. Для коментарів реалізовано шаблон C_COMMENTS_PATTERN. Класовою процедурою TRegEx.Replace виконується додавання до знайденого тексту тегу <b>.
Коментарі в PL\SQL починаються з двох мінусів "--" або обмежуються операторами /* та */. Для коментарів застосовується фарбування шрифту в зелений колір: <font color="#008080">$1</font>


function SqlSyntaxLight(const aSqlText: string): string;
const
  C_LEXEM_PATTERN = '(?i)(' +
  '\bAGGREGATE\b|\bALL\b|\bALTER\b|\bAND\b|\bANY\b|\bAS\b|\bASC\b|\bAVG\b|\bBEFORE\b|\bBEGIN\b|' +
  '\bBETWEEN\b|\bBULK\b|\bBY\b|\bCASE\b|\bCAST\b|\bCHAR\b|\bCHECK\b|\bCOLLECT\b|\bCOMMENT\b|' +
  '\bCOMMIT\b|\bCOUNT\b|\bCURRENT\b|\bCURRENT_USER\b|\bCURSOR\b|\bDATE\b|\bDAY\b|\bDEC\b|\bDECIMAL\b|' +
  '\bDECLARE\b|\bDEFAULT\b|\bDELETE\b|\bDESC\b|\bDISTINCT\b|\bEACH\b|\bELSE\b|\bELSIF\b|\bEND\b|' +
  '\bEXCEPTION\b|\bEXECUTE\b|\bEXISTS\b|\bFALSE\b|\bFETCH\b|\bFIRST\b|\bFOR\b|\bFORALL\b|\bFOUND\b|' +
  '\bFROM\b|\bFULL\b|\bFUNCTION\b|\bGROUPING\b|\bHAVING\b|\bIF\b|\bIN\b|\bINNER\b|\bINSERT\b|\bINTEGER\b|' +
  '\bINTERSECT\b|\bINTERVAL\b|\bINTO\b|\bIS\b|\bJOIN\b|\bLAST\b|\bLEFT\b|\bLEVEL\b|\bLIKE\b|\bLOOP\b|' +
  '\bMAX\b|\bMIN\b|\bMONTH\b|\bNEXT\b|\bNEXTVAL\b|\bNOT\b|\bNOTFOUND\b|\bNOWAIT\b|\bNULL\b|\bNULLS\b|' +
  '\bNUMBER\b|\bNUMERIC\b|\bOF\b|\bOLD\b|\bON\b|\bOR\b|\bORDER\b|\bOUT\b|\bOUTER\b|\bPLS_INTEGER\b|' +
  '\bPOSITIVE\b|\bPRIOR\b|\bPROCEDURE\b|\bRAISE\b|\bRANGE\b|\bRAW\b|\bREPLACE\b|\bRESULT\b|\bRETURN\b|' +
  '\bRIGHT\b|\bROLLBACK\b|\bROW\b|\bROWCOUNT\b|\bROWID\b|\bROWTYPE\b|\bSELECT\b|\bSELF\b|\bSET\b|\bSETS\b|' +
  '\bSTRING\b|\bSUBTYPE\b|\bSUM\b|\bSYSDATE\b|\bTABLE\b|\bTHEN\b|\bTIME\b|\bTIMESTAMP\b|\bTO\b|' +
  '\bTRANSACTION\b|\bTRIGGER\b|\bTRIM\b|\bTRUE\b|\bTYPE\b|\bUNDER\b|\bUNION\b|\bUNIQUE\b|\bUPDATE\b|' +
  '\bUROWID\b|\bUSE\b|\bUSER\b|\bUSING\b|\bVALUE\b|\bVALUES\b|\bVARCHAR\b|\bVARCHAR2\b|\bVARIABLE\b|' +
  '\bWHEN\b|\bWHERE\b|\bWHILE\b|\bWITH\b|\bXOR\b|\bYEAR\b'+
    ')';
  C_COMMENTS_PATTERN = '(?is)((/\*.*?\*/)|(--.*?\n))';
begin
   Result := TRegEx.Replace(aSqlText, C_LEXEM_PATTERN, '<b>$1</b>');
   Result := TRegEx.Replace(Result, C_COMMENTS_PATTERN, '<font color="#008080">$1</font>');
end;


Результат роботи даної функції можна бачити нижче:

select name 
from employee e  
join (select department_id, max(salary) as max_salary 
/*
Comment 1
*/
            from employee e2 
         group by department_id) d
  on d.department_id=e.department_id
--Comment 2
where e.salary = d.max_salary

Для виділення xml-тегів та атрибутів потрібно написати трішки складніший регулярний вираз:

const
  C_XML_PATTERN = '(?is)(<[\?\/]?)(\w+)(\s+\w+?=".*?")?([\?\/]?>)';
  C_ATTR_PATTERN = '(?is)\s+((\w+)="(.*?)")';

Але вся незручність  підсвічування xml-текстів полягає в тому, що переглядач сприймає xml-теги як частину html-документу, і тому, в кращому разі, просто некоректно відображує документ.
Тому для усунення такої ситуації використовується  таблиця перетворень символів між xml та html.

function EncodeXMLStr(const aValue: string): string;
const
  HSym : array[0..3] of string = ('&amp;','&lt;','&gt;','&quot;');
  TSym : array[0..3] of string = ('&'    ,'<'   ,'>'   ,'"'     );
var
  i    : integer;
  sTmp : string;
begin
  for i := 1 to Length(aValue) do
    if (Ord(aValue[i]) in [32, 40..58]) or (Ord(aValue[i]) >= 65)then
      sTmp := Concat(sTmp, aValue[i])
    else
    begin
      case aValue[i] of
       '<'  : sTmp := Concat(sTmp, '&lt;');
       '>'  : sTmp := Concat(sTmp, '&gt;');
       '&'  : sTmp := Concat(sTmp, '&amp;');
       '"'  : sTmp := Concat(sTmp, '&quot;');
       '''' : sTmp := Concat(sTmp, '&apos;');
      else
        sTmp := Concat(sTmp, '&#', IntToStr(Ord(aValue[i])), ';');
    end;
  end;
  Result := sTmp;
end;

Функція підсвічування xml-текстів з врахуванням перекодування буде виглядати так:

function XmlSyntaxLight(const aXmlText: string): string;
const
  C_XML_PATTERN  = '(?is)((<|&lt;)[\?\/]?)(\w+)(\s+\w+?&#61;&quot;.*?&quot;)?([\?\/]?(&gt;|>))';
  C_ATTR_PATTERN = '(?is)\s+((\w+)&#61;&quot;(.*?)&quot;)';
begin
  Result := EncodeXMLStr(aXmlText);
  Result := TRegEx.Replace(Result, C_XML_PATTERN, '<font color="blue">$1</font>' +
                                                  '<font color="brown">$3</font>' +
                                                  ' $4' +
                                                  '<font color="blue">$5</font>');
  Result := TRegEx.Replace(Result, C_ATTR_PATTERN, ' <font color="green">$2</font>' +
                                                   '<font color="navy">&#61;&quot;</font>' +
                                                   '<font color="black">$3</font>' +
                                                   '<font color="navy">&quot;</font>');
end;

Результат роботи функції:

<?xml version="1.0"?>
<Root >
<COMMON_PARAMS >
<Param18996 Date="41334" DateValue="01.03.2013" TabOrder="0"/>
<Param18997 Date="41435" DateValue="10.06.2013" TabOrder="1"/>
<Param18999 KeyList="1" KeyValue="1" TabOrder="2" Text="Some text"/>
<Param18998 KeyValue="2572" TabOrder="3" Text="Some text"/>
<Param19729 KeyValue="1766" TabOrder="4" Text="Some text"/>
<Param19730 KeyValue="1770" TabOrder="5" Text="Some text"/>
<Param19731 KeyValue="1847" TabOrder="6" Text="Some text"/>
<Param19000 KeyList="630" KeyValue="630" TabOrder="7" Text="630"/>
<Param19727 KeyList="" TabOrder="8" Text="Some text"/>
</COMMON_PARAMS >
</Root >

SyntaxHighlighter, наприклад, це робить так:

<?xml version="1.0"?>
<Root>
  <COMMON_PARAMS>
    <Param18996 Date="41334" DateValue="01.03.2013" TabOrder="0"/>
    <Param18997 Date="41435" DateValue="10.06.2013" TabOrder="1"/>
    <Param18999 KeyList="1" KeyValue="1" TabOrder="2" Text="Some text"/>
    <Param18998 KeyList="" KeyValue="2572" TabOrder="3" Text="Some text"/>
    <Param19729 KeyList="" KeyValue="1766" TabOrder="4" Text="Some text"/>
    <Param19730 KeyList="" KeyValue="1770" TabOrder="5" Text="Some text"/>
    <Param19731 KeyList="" KeyValue="1847" TabOrder="6" Text="Some text"/>
    <Param19000 KeyList="630" KeyValue="630" TabOrder="7" Text="630"/>
    <Param19727 KeyList="" TabOrder="8" Text="Some text"/>
  </COMMON_PARAMS>
</Root>

Кольорова гама підсвічування - справа смаку (і внутрішніх стандартів) кожного.
Детальніше про роботу з регулярними виразами в Delphi можна прочитати тут.

Немає коментарів :

Дописати коментар