태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

매우 오랜시간 Visual Studio를 켜놓고 쓰다보면 가끔 Search in files를 하면 검색이 안되고 (단순히 파일에서 문자열을 찾지 못하는게 아니라 아얘 파일조차 열 생각을 안합니다.) 아래와 같은 에러 메시지가 발생하는 경우가 있습니다.

No files were found to look in. Find was stopped in progress.

이건 Visual Studio의 버그라고 하더군요. 아주 어이없게도 이 문제를 해결하는 방법은 Ctrl + Scroll Lock을 누르는 것입니다. 버그가 발생했을 때, VIsual Studio안에서 Ctrl + Scroll Lock을 누르고, 찾기를 다시 한번 해보면 아주 정상적으로 찾기가 수행됩니다.

다른 곳에서도 이 버그가 컨트롤 + 스크롤 락으로 해결된다는 것에 대해 매우 어이없는 반응을 보이고 있습니다.

참고자료:
http://vidmar.net/weblog/archive/2007/04/17/Strangest-error-No-files-were-found-to-look-in.-Find.aspx


C++ STL의 string을 이용해서 구현한 replace all 함수입니다.

마지막 두줄의 assert를 보면 알 수 있듯이, 이 함수엔 문자열이 재귀적으로 치환되는 문제가 없습니다.

#include 
#include 
#include 

using namespace std;

string replaceAll(const string &str, const string &pattern, const string &replace)
{
	string result = str;
	string::size_type pos = 0;
	string::size_type offset = 0;

	while((pos = result.find(pattern, offset)) != string::npos)
	{
		result.replace(result.begin() + pos, result.begin() + pos + pattern.size(), replace);
		offset = pos + replace.size();
	}

	return result;
}

int main()
{
	assert(replaceAll("abcdefg", "bc", "de") == "adedefg");
	assert(replaceAll("abc\nabc\nabc", "\n", "") == "abcabcabc");
	assert(replaceAll("aaabbbaaa", "aaa", "1") == "1bbb1");
	assert(replaceAll("ababab ababab", "ab", "1") == "111 111");
	assert(replaceAll("abcdefg", "gf", "de") == "abcdefg");
	assert(replaceAll("aaa", "a", "aa") == "aaaaaa");
	assert(replaceAll("11aa11aa", "1", "11") == "1111aa1111aa");

	return 0;
}


Url Resolver클래스는 아래와 같은 기능들을 가지고 있습니다.

상대경로를 절대경로로 변환
상위 경로로 이동
Base URL 구하기

상대경로를 절대경로로 변환하는 기능에 북마크, 쿼리는 고려하지 않았으며, 일반적인 경우에 모두 정상적으로 동작합니다.

참고자료:
URL Relative Syntax and Base URLs - http://www.tcpipguide.com/free/t_URLRelativeSyntaxandBaseURLs-3.htm

URL 상대경로의 처리 - http://ljh131.tistory.com/6

	public class UrlResolver
	{
		public static string GetBaseUrl(string url)
		{
			string baseUrl = url.Substring(0, url.LastIndexOf('/') + 1);

			return baseUrl;
		}

		// return false if base url is root reached
		public static bool GoUpOnePath(string baseUrl, out string newBaseUrl)
		{
			// 루트인지 체크 ('/' 개수가 3개이하면 중단)
			int searchIndex = 0;
			int count = 0;
			int currentIndex = 0;

			while (true)
			{
				currentIndex = baseUrl.IndexOf('/', searchIndex);

				if (currentIndex != -1)
					count++;
				else
					break;

				searchIndex = currentIndex + 1;
			}

			if (count <= 3)
			{
				newBaseUrl = baseUrl;

				return false;
			}

			// go up one
			int secondOfLastSlashIndex = baseUrl.LastIndexOf('/', baseUrl.Length - 2);
			newBaseUrl = baseUrl.Substring(0, secondOfLastSlashIndex + 1);

			return true;
		}

		/*
		 * based on http://www.tcpipguide.com/free/t_URLRelativeSyntaxandBaseURLs-3.htm
		 * this function NOT include bookmark, query and new scheme url
		 */
		public static string GetAbsoluteUrl(string baseUrl, string relativeUrl)
		{
			// baseurl의 마지막은 반드시 /임
			Debug.Assert(baseUrl.LastIndexOf('/') == baseUrl.Length - 1);

			string[] relativePaths = { "//", "/", "./", "../", ".." };
			bool resolveRelativePath = false;

			do
			{
				resolveRelativePath = false;

				foreach (string relativePath in relativePaths)
				{
					if (relativeUrl.IndexOf(relativePath) == 0)
					{
						resolveRelativePath = true;

						switch (relativePath)
						{
							case "//":	// 호스트 네임 지정
								baseUrl = "http://";
								break;

							case "/":	// 루트 지정
								while (GoUpOnePath(baseUrl, out baseUrl))
									;
								break;

							case "./":	// 현재 디렉토리. 무시
								break;

							case "../":	// 한단계 아래 디렉토리로 감. (더 이상 갈 수 없을때는 진행하지 않음.)
								GoUpOnePath(baseUrl, out baseUrl);
								break;

							case "..":	// 한단계 아래 디렉토리로 감. (../와 같음)
								GoUpOnePath(baseUrl, out baseUrl);
								break;
						}

						// remove relative path from url
						relativeUrl = relativeUrl.Substring(relativePath.Length);

						// reset loop
						break;
					}
				}
			} while (resolveRelativePath);

			return baseUrl + relativeUrl;
		}
	}


프로그래머를 위한, 프로그래머의 폰트.

이름도 무려

Consolas Font Pack for Microsoft Visual Studio 2005

입니다.

Consolas, 11 point.
사용자 삽입 이미지

이렇게나 깔끔하고 미려한 Consolas폰트를 보자면 그동안 돋움체의 압박을 어떻게 감당해왔는지 의문입니다.

위의 사진에서는 11pt이지만 10pt가 가장 적절하더군요. 그럼 Consolas를 달고 즐겁게 코딩 해봅시다.

http://www.microsoft.com/downloads/details.aspx?familyid=22e69ae4-7e40-4807-8a86-b3d36fab68d3&displaylang=en

아마 Win32API를 배울때 두번째로 배우는 가장 기본적인 MessageBox함수는 모달 다이얼로그이긴 하지만 타이머와 함께 사용하면 더 이상 모달이 아닌게 됩니다.

타이머 내에서 MessageBox를 호출하면 이전 메시지박스가 있던지 없던지에 상관없이 무조건 뜹니다. 즉 메시지박스가 수도없이 뜨게 되죠. 이를 막는 가장 간단한 방법은, 타이머를 일시 중지 시키는 겁니다. 물론 프로그램 로직에 영향을 미치지 않는 타이머일때만 사용가능하겠죠. 그게 아니라면 프로그램의 요구사항에 맞춰서 적절한 방법을 고안해야죠.

private DialogResult SafeMessageBox(string text, string caption, MessageBoxButtons buttons)
{
	DialogResult result;

	if(changeCycleTimer.Enabled)
	{
		changeCycleTimer.Stop();

		result = MessageBox.Show(text, caption, buttons);

		changeCycleTimer.Start();
	}
	else
	{
		result = MessageBox.Show(text, caption, buttons);
	}

	return result;
}


리눅스에는 Regular Expression을 이용해서 파일 이름을 바꿀 수 있다는데, 그렇구나 하고 윈도우에서 쓰고싶어서 그냥 만들었음.

실행한 폴더내에서 패턴에 맞는 파일 이름들을 바꿔주는 기능.

# coding=euc-kr

import os
import re

def fileRename():
    fileList = os.listdir('.')
    
    print 'current dir:', os.getcwd()
    print 'file list:', fileList

    pattern = raw_input("\npattern (ex: '(?P[0-9]+).[0-9]+'): ").replace('\r', '')
    replace = raw_input("replace (ex: '\g'): ").replace('\r', '')
    
    print '\nfollowing files will be changed to:'
    
    for fileName in fileList:
        print fileName, '->', re.sub(pattern, replace, fileName)
        
    selection = raw_input('\nwant to change file names? (y/n/r): ').replace('\r', '')
    
    if selection == 'y':
        for fileName in fileList:
            os.rename(fileName, re.sub(pattern, replace, fileName))
        
        print '\nfile names successfully changed:', os.listdir('.')
    elif selection == 'n':
        print '\ncanceled'
    elif selection == 'r':
        print '\nretry'
        return False
    
    return True

if __name__ == '__main__':
    while fileRename() is not True:
        pass



사실 익스플로러 툴바, BHO(Browser Helper Object)를 만드는게 여간 힘든일이 아닙니다. COM을 써야할 뿐더러 일반적인 프로그램이 아니기 때문에 정보 조차 잘 없죠.

하지만 C#으로 만들면 정말 쉽습니다.

우선 전 코드 프로젝트를 참고했습니다.

Band Objects - .NET 2.0 Redux
http://www.codeproject.com/csharp/BandObjects20.asp?select=1819328&df=100&forumid=306921&exp=0

 
위의 소스 파일은 코드 프로젝트의 소스를 사용하기 쉽게 약간 수정한 파일입니다. 기본 구조는 코드 프로젝트 소스와 마찬가지로 BandObject클래스를 상속받는 것입니다.

우선 다음 두 개의 레퍼런스를 추가하도록 합니다. COM의 MSHTML(Microsoft HTML Object Library)과 SHDocVw(Microsoft Internet Controls) 입니다. 두 레퍼런스를 추가하면 Object Browser에서 이들 어셈블리를 볼 수 있습니다.

다음 단계는 BandObject클래스를 상속받아 실제 툴바를 구현하는 단계입니다. 아래 코드는 사용자 참여 웹 툴바에서 사용된 예이며, 툴바와 BHO 구현에 필요한 핵심적인 코드만 적어 놓았습니다. Guid는 자신의 Guid를 적으세요. 

[Guid("719FAB8A-8E67-4ba6-915D-4EE1AD3A2CEE")]
[BandObject("사용자 참여 웹 툴바", BandObjectStyle.Horizontal | BandObjectStyle.ExplorerToolbar, HelpText = "사용자 참여 웹 툴바")]
public class UpwBandObject : BandObject
{
	public UpwBandObject()
	{
		InitializeComponent();

		minSize = new Size(100, 27); // 최소 크기를 반드시 적어줘야 합니다.

		SetSiteEvent += new SetSiteEventHandler(UpwBandObject_SetSiteEvent);
		ShowDWEvent += new ShowDWEventHandler(UpwBandObject_ShowDWEvent);
	}
	private void UpwBandObject_ShowDWEvent(bool fShow)
	{
		toolbarVisible = fShow;

		RefreshCurrentSite();
	}

	private void UpwBandObject_SetSiteEvent(object pUnkSite)
	{
		if (pUnkSite != null)
		{
			// add events
			webBrowserClass.BeforeNavigate2 += 
new DWebBrowserEvents2_BeforeNavigate2EventHandler(webBrowser_BeforeNavigate2);
			webBrowserClass.NavigateComplete2 += 
new DWebBrowserEvents2_NavigateComplete2EventHandler(webBrowser_NavigateComplete2);
			webBrowserClass.DocumentComplete += 
new DWebBrowserEvents2_DocumentCompleteEventHandler(webBrowser_DocumentComplete);
		}
		else
		{
			// remove events
			webBrowserClass.BeforeNavigate2 -= new DWebBrowserEvents2_BeforeNavigate2EventHandler(webBrowser_BeforeNavigate2);
			webBrowserClass.NavigateComplete2 -= new DWebBrowserEvents2_NavigateComplete2EventHandler(webBrowser_NavigateComplete2);
			webBrowserClass.DocumentComplete -= new DWebBrowserEvents2_DocumentCompleteEventHandler(webBrowser_DocumentComplete);
		}
	}

	private void webBrowser_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName,
			  ref object PostData, ref object Headers, ref bool Cancel)
	{
	}

	private void webBrowser_NavigateComplete2(object pDisp, ref object URL)
	{
	}

	private void webBrowser_DocumentComplete(object pDisp, ref object URL)
	{
	}

	private void Navigate(string url)
	{
		object flags = null;
		object targetFrameName = null;
		object postData = null;
		object headers = null;

		webBrowserClass.Navigate(url, ref flags, ref targetFrameName, ref postData, ref headers);
	}
}


웹 브라우저 클래스의 이벤트들이 바로 BHO부분입니다. 이벤트에 관련된 부분은 Visual Studio의 Object Browser를 보면서 추가할 수 있습니다. 물론 MSDN에도 짧게나마 설명이 있습니다.

UserControl을 상속받으면 Visual Studio에서는 Designer로 폼을 편집할 수 있습니다. View Designer를 클릭해서 나오는 화면이 툴바에 나오게 될 화면입니다. 그 부분은 개발하고자 하는 툴바에 맞춰서 디자인 하시면 됩니다.

마지막 단계는 실제 툴바를 익스플로러에 등록하는 과정입니다. 우선 프로젝트 속성의 Assembly Information에서 Make assembly COM-Visible을 체크하고 컴파일을 합니다. 또 Strong name을 위해 Signing assembly (.snk)가 필요한데 이것 역시 프로젝트 속성에서 만들 수 있습니다.

그리고 빌드 후에 다음 명령을 실행 시킵니다.

cd $(ProjectDir)bin\Debug

"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil" /if MyToolbar.dll
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm" MyToolbar.dll

익스플로러를 열면 툴바 영역에서 등록된 툴바가 메뉴에 표시됩니다.

디버깅은 프로퍼티의 Start Action에 externel program으로 익스플로러를 지정하면 됩니다.

혹시 궁금증 있으신 분은 코멘트를 남기시면 답변 드리겠습니다.



정규식이 참 외계어 같아서 쓸때는 어떻게 쓴다고 해도 쓰고나면 알아보기가 힘듭니다.

하지만 여기 MS직원이 만든 Regular Expression Workbench를 쓰면 전혀 문제될게 없습니다.

1. 정규식에 마우스를 갖다대면 그 의미를 툴팁으로 표시해줍니다.

2. 게다가 실시간으로 정규식을 실행해볼 수 있습니다.

3. 정규식 기억이 잘 안나도 Add Item 메뉴를 이용하면 쉽게 쓸 수 있습니다.

4. 정규식을 C#소스로 복사할 수 있는 기능은 덤입니다.

정규식을 쓴다면 반드시 필요한 프로그램이죠.

아래 링크에서 받으세요.

http://blogs.msdn.com/ericgu/archive/2003/07/07/52362.aspx

사용자 삽입 이미지


C#에서 DLL의 Unmanaged function을 호출하려면 다음과 같은 방법으로 함수를 만들어야 합니다. 아래 글의 출처는 msdn입니다.

To consume exported DLL functions

1. Identify functions in DLLs.
Minimally, you must specify the name of the function and name of the DLL that contains it.

2. Create a class to hold DLL functions.
You can use an existing class, create an individual class for each unmanaged function, or create one class that contains a set of related unmanaged functions.

3. Create prototypes in managed code.
[C#] Use the DllImportAttribute to identify the DLL and function. Mark the method with the static and extern modifiers.

4. Call a DLL function.
Call the method on your managed class as you would any other managed method. Passing structures and implementing callback functions are special cases.

가장 일반적인 방법이죠. 좀 복잡할테니 예를 들어서 설명하겠습니다.

우리가 필요한 함수는 Win32API함수인 EnumDisplayDevice()라는 함수입니다. 함수의 프로토타입은 다음과 같습니다.

BOOL EnumDisplayDevices(
  LPCTSTR lpDevice,                // device name
  DWORD iDevNum,                   // display device
  PDISPLAY_DEVICE lpDisplayDevice, // device information
  DWORD dwFlags                    // reserved
);
이제 이 함수 프로토타입을 C#용으로 고쳐야 합니다.

가장 먼저 할 일은 함수가 있는 DLL파일입니다. 이건 MSDN을 통해 쉽게 찾을 수 있죠. User32.dll에 있다는 것이 바로 확인됩니다.

두번째는 본격적으로 함수의 프로토타입을 C#으로 만드는 과정입니다.

우선 첫번째 인자를 볼까요? 문자열을 나타내는 LPCTSTR입니다. long pointer to const string이죠. 이 타입을 어떻게 바꿔야 할까요?

여기서 반드시 참고해야 할 것이 MSDN의 Platform Invoke Data Types 표입니다.

http://msdn2.microsoft.com/en-us/library/ac7ay120(vs.80).aspx

이 표를 보면 Win32API나 C스타일 함수에서 사용되는 데이터 타입들에 대응되는 Managed class type들이 나와있습니다. 표를 보면서 C#에서의 파라매터 타입을 정해주면 됩니다. 표에 따르면 - LPCTSTR은 직접 나와있지 않지만 - 얼추 string에 대응됩니다.

마찬가지로 DWORD는 UInt32입니다. 문제는 세번째 인자인 PDISPLAY_DEVICE인데요, 구조체의 포인터죠.

아쉽게도 이 경우에 대해서는 MSDN에서 명확한 해답을 찾을 수 없었습니다. 하지만 제가 알아본 바로는 대략 구조체를 클래스로 변환한 뒤, In, Out Attribute을 결정해주면 됩니다.

구조체인 DISPLAY_DEVICE에 대응되는 클래스를 만들기 전에 프로토타입 부터 설정해봅시다. MSDN을 보면 이 파라매터는 out 타입입니다. 따라서 In과 Out Attribute를 넣어줍니다. 완성된 프로토타입은 다음과 같습니다.

  [DllImport("user32.dll")]
  public static extern bool EnumDisplayDevices(string lpDevice, UInt32 iDevNum,
             [In, Out] DisplayDevice lpDisplayDevice, UInt32 dwFlags);
이제 할일은 구조체 DISPLAY_DEVICE를 클래스로 변환하는 작업입니다. 구조체의 프로토타입을 봅시다.

typedef struct _DISPLAY_DEVICE {
  DWORD cb;
  TCHAR DeviceName[32];
  TCHAR DeviceString[128];
  DWORD StateFlags;
  TCHAR DeviceID[128];
  TCHAR DeviceKey[128];
} DISPLAY_DEVICE, *PDISPLAY_DEVICE;
여기서 문제는 배열이죠. C#의 배열은 C의 배열과 메모리 할당 방식이 다릅니다. 이 문제는 MarshalAs Attribute를 이용해서 해결할 수 있습니다.

완성된 클래스는 아래와 같습니다.

 [StructLayout(LayoutKind.Sequential)]
 public class DisplayDevice
 {
  public UInt32 cb;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
  public string DeviceName;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceString;  
  public UInt32 StateFlags;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceID;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceKey;
 }
구조체가 선언된 순서대로 할당되는 것처럼 클래스 Attribute도

 [StructLayout(LayoutKind.Sequential)]

로 만들어서 구조체와 마찬가지로 순차적으로 변수를 할당한다고 선언을 해주면 됩니다.

드디어 프로토타입이 완성되었습니다!

하지만 실제로 함수를 호출하려면 아직 남은 것이 한가지 있습니다.

'Before calling EnumDisplayDevices, you must initialize the cb member of DISPLAY_DEVICE to the size, in bytes, of DISPLAY_DEVICE.'

함수를 호출하기 전에 구조체(여기서는 클래스)의 cb멤버를 구조체의 크기로 설정해야 한다는 것입니다. C에선 sizeof(DISPLAY_DEVICE)로 가능합니다. 하지만 C#에서는 어떻게 해야할까요?

물론 C#에도 sizeof키워드가 있습니다만 여기서는 동작하지 않습니다. 이 경우에도 마샬을 이용해야 하는데, 코드는 아래와 같습니다.

   DisplayDevice dd = new DisplayDevice();
   dd.cb = (UInt32)Marshal.SizeOf(dd);
이제 EnumDisplayDevices함수를 C#에서 호출할 수 있게 되었습니다.

기타 함수들에서 사용되는 상수들은 직접 헤더 파일에서 찾아서 넣어주면 됩니다.

자세한 설명이 필요하면 아래 링크를 참고하세요

http://www.canaware.com/default.aspx?page=ed51cde3-d979-4daf-afae-fa6192562ea9&article=cfd2e407-d20f-4dfc-8c34-e7d29c771b05

말그대로 컨픽 파일을 손수 만들 필요없이 자동으로 해줍니다.

과연 얼만큼 자동으로 해줄까요?

일단 컨픽 파일은 아래와 같은 XML파일입니다.


윈9X에서는 INI파일을 사용했지만 시대가 시대인만큼 컨픽 파일에서도 XML을 쓰는군요.

이 부분이 바로 우리 컨픽 내용입니다.

소스를 통해서 볼까요?

참고로 프로젝트 레퍼런스에서 System.configuration을 추가해야 된답니다.

using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Reflection;
namespace ConfigTest
{
	public class MySection : ConfigurationSection
	{
		[ConfigurationProperty("fileName", DefaultValue = "default.txt")]
		[StringValidator(InvalidCharacters = " ~!@#$%^&*()[]{}/;'\"|\\", MinLength = 1, MaxLength = 60)]
		public string FileName
		{
			get
			{
				return (string)this["fileName"];
			}
			set
			{
				this["fileName"] = value;
			}
		}

		[ConfigurationProperty("maxUsers", DefaultValue = (long)10000)]
		[LongValidator(MinValue = 1, MaxValue = 1000000, ExcludeRange = false)]
		public long MaxUsers
		{
			get
			{
				return (long)this["maxUsers"];
			}
			set
			{
				this["maxUsers"] = value;
			}
		}
	}

	class Program
	{
		static void CreateSection()
		{
			try
			{
				const string MySectionName = "MySection";
				MySection customSection;

				// open config file
				Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

				if (config.Sections[MySectionName] == null)
				{
					customSection = new MySection();

					config.Sections.Add(MySectionName, customSection);

					customSection.SectionInformation.ForceSave = true;
					config.Save(ConfigurationSaveMode.Full);
				}

				// read
				MySection section = config.Sections[MySectionName] as MySection;

				Console.WriteLine(section.FileName);

				// modify
				section.FileName = "new_file.txt";

				config.Save(ConfigurationSaveMode.Full);

				Console.WriteLine(section.FileName);
			}
			catch (ConfigurationErrorsException err)
			{
				Console.WriteLine(err.ToString());
			}
		}

		static void Main(string[] args)
		{
			CreateSection();
		}
	}
}
C#의 재미있는 기능 중 하나인 리플렉션을 사용했군요. ConfigurationProperty를 이용해서 어떤 이름으로 저장할 것인지, 게다가 디폴트 값까지 지정할 수 있습니다.

아래 Validator는 ASP.NET에서나 보던 것이군요. ASP.NET과 마찬가지로 문자열에서 안되는 문자라던지 정수형에서 변수 값 범위 등을 설정할 수 있습니다.

별다른 변수 생성을 통해서는 안되고 저런식으로 '리플렉션과 프로퍼티'를 이용해야 한답니다. 개인적으로 프로퍼티를 잘 사용하는 편은 아니지만 이만큼 편하다면 어쩔 수 없지요.

아무튼 저런식으로 지정하고 나면, 클래스를 통채로 저장하는 듯한 기분입니다. 클래스 내에서 지정만 해주면 저장하고 읽는 부분은 매우 간단합니다.

CreateSection() 메소드에서는 컨픽 파일을 열고 - 없으면 생성한 후 - 컨픽을 로드한 후, 다시 수정하고 저장하고 값을 확인하는 테스트를 합니다.

참 편리하죠?

참고:
http://msdn2.microsoft.com/en-us/library/system.configuration.configurationpropertyattribute(vs.80).aspx



이미지 폴더에 그림이 많을 때 그림들을 웹 브라우저로 볼 수 있게 HTML Gallery 파일을 만들어주는 파이썬 스크립트 입니다.

폴더에 그림이 수백장씩 있으면 ACDSee나 알씨같은 프로그램으로 마우스 휠 돌려가면서 한장 한장씩 보는것도 고통스럽죠. 이 스크립트는 폴더 안의 모든 그림 파일을 '원본 크기'로 한 HTML파일(혹은 그림이 너무 많다면 나눠서)에 넣어줍니다. 그저 HTML파일을 열어서 스크롤만 하면 편하게 볼 수 있습니다.

아래 스크립트를 파이썬 스크립트 파일로 저장하고 실행하면 됩니다.

import os 
def getFileListInCurDir():
 """ get file list """
 path = os.getcwd()
 return os.listdir(path)

def extractImageFileList(fileList, replacePercentSign = True):
 """ get image list """
 exts = ('.jpg', '.gif', '.bmp', '.png')
 imageFileList = []

 for ext in exts:
  for fileName in fileList:
   if fileName.find(ext) != -1:
    if replacePercentSign and fileName.find('%') != -1:
     newFileName = fileName.replace('%', '$')
     os.rename(fileName, newFileName)
     fileName = newFileName
     
    imageFileList.append(fileName)
 
 return imageFileList

class HtmlGalleryFile:
 def __init__(self, fileNameOnly, fileCount, align):
  self.fileName = fileNameOnly + repr(fileCount) + '.html'
  self.file = open(self.fileName, 'w')
  
  self.writeStartTag(align)
  
 def close(self, notifyDone = True):
  self.writeEndTag()
  
  self.file.close()
  
  if notifyDone:
   print '%s successfully done' % self.fileName
 
 def writeStartTag(self, align): 
  self.file.write('')
  self.file.write('

' % align) def writeEndTag(self): self.file.write('

') self.file.write('') def write(self, tag): self.file.write(tag) def getFile(self): return self.file def makeHtmlImageGallery(fileNameOnly, imageFileList, maxCountPerHtml = -1, align = 'center'): fileCount = 0 imageCount = 0 html = HtmlGalleryFile(fileNameOnly, fileCount, align) for fileName in imageFileList: if maxCountPerHtml != -1 and imageCount >= maxCountPerHtml: imageCount = 0 fileCount += 1 html.close() html = HtmlGalleryFile(fileNameOnly, fileCount, align) s = '
' % fileName html.write(s) imageCount += 1 html.close() def getOptions(): options = {} fileNameOnly = raw_input('html file name(without extension, default: image): ') fileNameOnly = fileNameOnly.replace('\r', '') if fileNameOnly == '': fileNameOnly = 'image' maxCountPerHtml = raw_input('max image count per html (default: unlimited): ') maxCountPerHtml = maxCountPerHtml.replace('\r', '') if maxCountPerHtml == '': maxCountPerHtml = -1 else: maxCountPerHtml = int(maxCountPerHtml) replacePercentSign = True align = 'center' options['fileNameOnly'] = fileNameOnly options['maxCountPerHtml'] = maxCountPerHtml options['replacePercentSign'] = replacePercentSign options['align'] = align return options if __name__ == "__main__": options = getOptions() fileList = getFileListInCurDir() print 'getting file list done.' imageList = extractImageFileList(fileList, options['replacePercentSign']) print 'extract image file list done. %d image(s) found.' % len(imageList) makeHtmlImageGallery(options['fileNameOnly'], imageList, options['maxCountPerHtml'], options['align']) print 'all progress done.'


DOM과 Javascript로 HTML문서를 트리 구조의 객체로 접근할 수 있습니다.

여기서 말하는 객체는 HTML의 element를 말합니다. html - body - div식의 각 HTML 태그들이 하나의 객체로 표현되죠.

이때 HTML문서의 어느 객채에 접근할 건지를 나타내는 방법으로 XPath(XML Path Language)를 사용합니다.

예를 들어 다음의 XPath를 보면,

//BODY[0]/DIV[8]/SPAN[0] 는

HTML문서내의 첫 번째 BODY의 자식 중 8번째 DIV내의 0번째 SPAN을 가리킵니다.

그리고 아래의 자바스크립트는 XPath를 구하는 함수입니다. getXPath()를 호출하면 되는데 예제와 함께 봅시다.

실행 결과는

//BODY[0]/DIV[0]/SPAN[0]

입니다.

이제, XPath를 구했다면 이 경로로 다시 HTML객체를 얻어올 수 있어야 합니다. 다음의 자바스크립트 함수는 지정된 XPath에 해당하는 HTML객체를 가져오는 함수입니다.

함수의 인자로 XPath를 넣어주면 됩니다.

위의 두 함수는 XML에서 사용되는 XPath와 형식이 동일합니다. 따라서 XPath와 관련된 다양한 함수를 적용할 수 있습니다.