Sync to trunk.

This commit is contained in:
John Schember 2011-08-18 16:48:24 -04:00
commit c3bf538fc0
46 changed files with 847 additions and 119 deletions

98
imgsrc/languages.svg Normal file
View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="128" width="128" version="1.0" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 256 256">
<defs>
<linearGradient id="b" y2="158.07" gradientUnits="userSpaceOnUse" x2="141.27" gradientTransform="matrix(1.68, 0, 0, 1.68, -86.7, -86.7)" y1="70.428" x1="141.27">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#00a200" offset="1"/>
</linearGradient>
<linearGradient id="a" y2="158.07" gradientUnits="userSpaceOnUse" y1="70.428" gradientTransform="matrix(1.68, 0, 0, 1.68, -86.7, -86.7)" x2="141.27" x1="141.27">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#00a100" offset="0.5"/>
<stop stop-color="#000" offset="1"/>
</linearGradient>
<linearGradient id="c" y2="397.34" gradientUnits="userSpaceOnUse" x2="12.991" gradientTransform="matrix(2.573, 0, 0, -2.573, 207.924, 1307.73)" y1="397.34" x1="-117">
<stop stop-color="#0053BD" offset="0"/>
<stop stop-color="#0032A4" offset="1"/>
</linearGradient>
<radialGradient id="d" gradientUnits="userSpaceOnUse" cy="439.63" cx="-57.022" gradientTransform="matrix(2.573, 0, 0, -2.573, 207.924, 1307.73)" r="98">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#57ADFF" offset="0.6"/>
<stop stop-color="#C9E6FF" offset="1"/>
</radialGradient>
<linearGradient id="e" y2="183.37" gradientUnits="userSpaceOnUse" x2="127.66" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="63.215" x1="127.66">
<stop stop-color="#006a00" offset="0"/>
<stop stop-color="#004000" offset="0.2"/>
<stop stop-color="#00d000" offset="1"/>
</linearGradient>
<linearGradient id="f" y2="361.42" gradientUnits="userSpaceOnUse" x2="-52.251" gradientTransform="matrix(2.573, 0, 0, -2.573, 207.924, 1307.73)" y1="457.03" x1="-52.251">
<stop stop-color="#FFF" offset="0"/>
<stop stop-color="#94CAFF" offset="1"/>
</linearGradient>
<linearGradient id="g" y2="158.07" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="141.27" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="70.428" x1="141.27"/>
<linearGradient id="h" y2="130.03" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="100.51" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="70.033" x1="100.51"/>
<linearGradient id="i" y2="85.32" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="120.48" gradientTransform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" y1="68.117" x1="120.48"/>
<linearGradient id="j" y2="79.161" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="124.57" y1="73.444" x1="124.57"/>
<linearGradient id="k" y2="73.865" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="132.78" y1="67.756" x1="132.78"/>
<linearGradient id="l" y2="323.36" gradientUnits="userSpaceOnUse" x2="258.77" gradientTransform="translate(5.58, -12.8322)" y1="408.7" x1="258.77">
<stop stop-color="#3434ff" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="m" y2="85.792" gradientUnits="userSpaceOnUse" x2="-60.735" gradientTransform="translate(2.16, -1.33)" y1="171.13" x1="-60.735">
<stop stop-color="#ffff01" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="n" y2="298.71" gradientUnits="userSpaceOnUse" x2="-105.42" y1="384.04" x1="-105.42">
<stop stop-color="red" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="o" y2="408.7" gradientUnits="userSpaceOnUse" x2="32.595" gradientTransform="translate(-3.45, -0.43)" y1="494.61" x1="32.595">
<stop stop-color="lime" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
<linearGradient id="p" y2="99.849" gradientUnits="userSpaceOnUse" x2="230.67" gradientTransform="translate(1.59, 1.61)" y1="171.13" x1="230.67">
<stop stop-color="#F0F" offset="0"/>
<stop stop-color="#b9b9b9" offset="1"/>
</linearGradient>
</defs>
<g transform="translate(-3.417, 1.068)">
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M-39.634,171.47c-31.743,31.66-49.227,73.53-49.227,117.89,0,92.35,75.02,167.48,167.23,167.48,92.218,0,167.25-75.13,167.25-167.48,0-92.06-75.03-166.96-167.25-166.96-44.38,0.01-86.288,17.43-118.01,49.07z" fill="#000"/>
<path d="M-43.9,167.2c-31.744,31.66-49.228,73.53-49.228,117.89,0,92.35,75.02,167.48,167.23,167.48,92.225,0,167.24-75.13,167.24-167.48,0-92.06-75.02-166.96-167.24-166.96-44.38,0.01-86.287,17.43-118,49.07z" fill="url(#c)"/>
<path d="M-39.03,172.09c-30.439,30.35-47.207,70.49-47.207,113,0,88.55,71.929,160.59,160.34,160.59,88.42,0,160.35-72.04,160.35-160.59,0-88.25-71.93-160.06-160.35-160.06-42.533,0.01-82.714,16.72-113.13,47.06z" fill="#b0d9ff"/>
<path d="M74.105,440.51c85.675,0,155.18-69.8,155.18-155.42,0-85.08-69.51-154.88-155.18-154.88-85.068,0-155.16,69.8-155.16,154.88,0,85.62,70.098,155.42,155.16,155.42z" fill="#FFF"/>
<path d="M74.105,440.51c85.675,0,155.18-69.8,155.18-155.42,0-85.08-69.51-154.88-155.18-154.88-85.068,0-155.16,69.8-155.16,154.88,0,85.62,70.098,155.42,155.16,155.42z" fill="url(#d)"/>
<path d="M22.564,147.28c-0.767,0-1.608,0.31-2.467,0.8,2.019-0.59,2.969-0.8,2.467-0.8m49.961,9.74l0.502-7.31-7.712,0.49,1.019,6.82h6.191m-87.044,126.31c-1.554-1.46-0.512-7.81-0.512-7.81s-23.183-12.18-48.417-19.5c-3.111-0.9-1.541-7.31,1.539-9.74l-1.022-6.84c-0.512-3.41,5.157-19.99,10.82-21.44,5.669-1.47-0.517,9.74-0.517,9.74l-5.661,3.41s6.693,7.8,8.244,7.8c1.542,0,4.117-3.9,4.117-3.9l-7.207-4.87,6.695-2.93,0.422-2.57,1.125-0.36,11.087-16.79c7.662-3.14,17.08-7.02,18.276-7.59,2.064-0.97,16.478-9.26,19.056-11.2,2.5833-1.97,8.2368-1.47,10.292-1.47,2.072,0,5.157-0.98,5.669-6.35,0.514-5.36,2.578-6.34,4.127-4.87,1.549,1.45-1.549,3.9,2.061,4.87,3.602,0.98,6.69,3.42,9.273,0.98,1.881-1.78-0.638-3.82-2.336-5.35h28.599l3.096-8.8-6.695-0.97-24.735-2.43v-2.93l-1.971,0.33c2.679-14.71,18.58-12.29,6.608-20.8-0.729-0.53-11.203,16.5-13.869,16.17-4.83-0.64-11.067-0.69-12.395,0.88-1.7595,2.08,3.95-7.13,8.862-9.92-7.845,2.31-32.626,10.79-58.82,38.7-25.046,26.67-35.032,62.62-35.032,63.88,0,2.43,5.156,3.41,5.668,6.34,0.518,2.91-9.785,12.68-9.785,17.55,0,2.25-2.426,32.3,6.17,57.06,10.051,28.89,31.505,53.03,34.001,54.62l5.146-2.44s-11.847-20.97-12.359-22.93c-0.509-1.94,13.391-30.23,20.093-29.24,6.693,0.95,5.152,2.92,9.271,0.48,4.122-2.43,6.6951-22.44,11.332-24.38,4.6392-1.96,9.7853-4.39,9.2733-9.27-0.5378-4.89-18.047-13.66-19.589-15.12m111.77-143.37l-13.905-4.88,2.578,7.81,11.327-2.93m-52.022,18.04c1.552,0,32.449-20.47,29.359-20.96-3.08-0.49-3.598,0-11.854-0.98-8.229-0.98-16.993,11.7-19.049,13.66-2.061,1.95-1.366,8.28,1.544,8.28m141.8,147.04l3.99-5.18-3.99-1.4-2.99,3.76-3.48,5.16,2.99,1.41,3.48-3.75m17.45,16.93l-1-7.53h-6.48l-0.5,5.64-5.99-0.93-1.48-6.12-2.99-1.88-3.5,4.23-3.48-0.94-1,3.29,3.99,0.95v30.54l14.11,3.4c-0.33,0.56-0.57,1.03-0.65,1.29-1.01,3.29,3.98,4.72,7.46,3.29,1.3-0.51,5.4-4.66,7.98-10.8,3.59-8.54,6.73-19.82,7.36-23.8l1.61-3.45-9.97,3.75-5.49-0.93h0.02m20.94-56.88l-2.61-5.04c-3.08-17.39-10.15-39.64-25.3-62.64-22.8-34.57-86.26-54.51-86.26-54.51l-3.48,4.22-2-3.28-4.99-1.89v4.24l4.5,3.76-2.99,1.41-11.476,0.93-25.918,14.11,2.488,11.28-2.997,0.94-1.483,2.35,8.474,12.68,0.496,4.24-6.978,1.41v8.45l-3.986,0.94,0.5,6.58-33.906,23.52,0.999,13.14c2.488,3.29,21.935,23.04,21.935,23.04s22.429,0.92,27.413-1.88c4.987-2.82,1.493,2.82,2.995,4.23,1.488,1.42,1.989,11.28,3.482,12.22,1.495,0.93,0,6.57,1.994,8.46,1.991,1.87,1.991,24.44,1.991,24.44s11.967,20.2,11.967,25.37c0,5.18-0.504,4.7,8.97,4.24,9.48-0.47,11.47-4.24,13.45-5.64,2.01-1.41,2.01-4.7,4-7.52,2-2.83,5.48-13.63,9.98-17.39,4.48-3.77,16.45-6.59,17.43-13.16,1-6.58,5.49-11.75,5.49-11.75l21.55-22.8-0.6,3.06-0.5,11.74,6.48-2.34-0.49-12.7-2.33-2.46,0.33-0.36s-1.49-2.82-3.48-2.82-13.97,2.82-15.95,2.35c-2-0.47-10.48-23.03-11.97-23.96-1.5-0.94-10.97-16.46-10.97-16.46s21.93,26.32,25.43,36.66c2.02,6.02,9.63,0.41,15.82-5.87l1.64,4,3.98-0.95-0.5-4.7h4.48v7.05l-1.49,3.76-0.51,6.1,3.99,3.77,2-3.28,6.47-6.12,7.48-3.76,2,3.76,1,5.18-2,5.63-3.99,3.29-1.99,8.46v4.23l-4.48-2.82-0.49-8.93-6.49,0.48-2.99,7.98,4.49,6.59,10.46,1.41,8.48-8,1-15.49,3.77-4.98c2.45,6.31,4.21,12.92,4.21,19.08,0,6.74,3.08-4.68,0.46-25.26l1.52-2.02m-125.63-47.94l-26.917-0.95,11.46-9.39h5.983l9.474,6.57v3.77m32.913-3.29v4.23h-11.46l0.99,2.83-6.98,0.95-0.49,2.34-4.99-0.93-8.98-1.89,1.5-2.34,1.5-2.84,4.98-5.16,2,3.76,7.48-0.48,3.98-4.23,15.46,2.82-4.99,0.94m0.98-6.11l-5.97,0.94-1-4.24,7.47-0.93,1-4.23,5.5,5.65-7,2.8v0.01m28.93,146.16l-3.5,2.82,0.5,7.06h4.49v-6.1l3.99-5.18v-10.81l-2.5-0.48-2.98,12.69m-33.4-14.08s-3.49,0.91,0.49,2.33,19.94-23.01,19.94-23.01l-13.46,8.45-6.98,12.23h0.01m-27.14,90.07l-2.978-2.84-5.985-0.95-0.991,2.84-7.976-0.94-0.499-3.78h-5.98l-6.476,3.78h-11.458l-0.996-2.84-18.433-1.9-2.995,2.84-7.462-1.88-1.001-6.63-3.487-0.49-3.988,7.12-13.452-0.47c2.4088,1.13,22.491,13.12,53.301,15.61,40.856,3.31,60.296-6.62,60.296-6.62l-1.5-1.43-17.94-1.41v-0.01z" fill="url(#e)"/>
<path d="M73.583,254c53.147,0,99.387-18.31,123.18-45.31-23.96-45.72-70.24-76.92-123.41-76.92-52.699,0-99.045,31.1-123.15,76.69,23.726,27.12,70.075,45.54,123.38,45.54z" fill="url(#f)"/>
<path d="M141.67,229.84s3.61,4.33,8.13,10.17c19.53-7.86,35.75-18.61,46.96-31.32-7.94-15.15-18.35-28.7-30.64-40.06-25.84-16.6-54.87-25.73-54.87-25.73l-3.48,4.22-2-3.28-4.99-1.89v4.24l4.5,3.76-2.99,1.41-11.476,0.93-25.918,14.11,2.488,11.28-2.997,0.94-1.483,2.35,8.474,12.68,0.496,4.24-6.978,1.41v8.45l-3.986,0.94,0.5,6.58-33.906,23.52,0.803,10.55c14.209,3,29.423,4.66,45.276,4.66,27.537,0,53.187-4.94,74.847-13.44-3.12-4.76-6.76-10.72-6.76-10.72zm-41.883-12.69l-26.917-0.95,11.46-9.39h5.983l9.474,6.57v3.77zm32.913-3.29v4.23h-11.46l0.99,2.83-6.98,0.95-0.49,2.34-4.99-0.93-8.98-1.89,1.5-2.34,1.5-2.84,4.98-5.16,2,3.76,7.48-0.48,3.98-4.23,15.46,2.82-4.99,0.94zm0.98-6.12v0.01l-5.97,0.94-1-4.24,7.47-0.93,1-4.23,5.5,5.65-7,2.8z" fill="url(#g)"/>
<path d="M-15.54,199.94c2.063-0.97,16.478-9.26,19.056-11.2,2.5833-1.97,8.2361-1.47,10.292-1.47,2.071,0,5.156-0.98,5.669-6.35,0.514-5.36,2.578-6.34,4.127-4.87,1.549,1.45-1.549,3.9,2.061,4.87,3.602,0.98,6.69,3.42,9.273,0.98,1.881-1.78-0.638-3.82-2.336-5.35h28.599l3.095-8.8-6.695-0.97-24.734-2.43v-2.93l-1.971,0.33c2.678-14.71,18.58-12.29,6.607-20.8-0.728-0.53-11.203,16.5-13.868,16.17-4.83-0.64-11.067-0.69-12.395,0.88-1.7598,2.08,3.95-7.13,8.862-9.92-6.106,1.8-22.478,7.38-41.781,22.9-11.17,10.8-20.701,23.45-28.121,37.48,2.789,3.19,5.918,6.25,9.309,9.18l6.674-10.11c7.663-3.13,17.083-7.02,18.277-7.59z" fill="url(#h)"/>
<path d="M43.679,149.72c-2.059,1.95-1.364,8.28,1.546,8.28,1.552,0,32.449-20.47,29.359-20.96-3.08-0.49-3.598,0-11.854-0.98-8.232-0.98-16.996,11.7-19.051,13.66z" fill="url(#i)"/>
<polygon points="126.07,73.444,123.08,73.631,123.47,76.284,125.88,76.284,125.88,76.285,126.07,73.444" transform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" fill="url(#j)"/>
<polygon points="135.49,69.653,130.08,67.756,131.08,70.792,135.49,69.653" transform="matrix(2.573, 0, 0, 2.573, -251.365, -39.26)" fill="url(#k)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path d="M247.4,375.48l15.77,2.64c-2.02,5.79-5.23,10.19-9.6,13.21-4.38,3.02-9.85,4.54-16.42,4.54-10.4,0-18.1-3.4-23.09-10.2-3.95-5.44-5.92-12.32-5.92-20.61,0-9.92,2.59-17.68,7.78-23.3,5.18-5.61,11.73-8.42,19.65-8.42,8.9,0,15.92,2.94,21.07,8.82,5.14,5.88,7.6,14.88,7.38,27.01h-39.66c0.12,4.69,1.39,8.34,3.83,10.95,2.45,2.61,5.49,3.92,9.13,3.92,2.48,0,4.56-0.68,6.25-2.03s2.97-3.53,3.83-6.53zm0.9-16c-0.11-4.58-1.29-8.07-3.55-10.45-2.25-2.38-4.99-3.58-8.22-3.58-3.45,0-6.31,1.26-8.56,3.78-2.25,2.51-3.36,5.93-3.32,10.25h23.65zm-20.22-32.11l7.78-16.84h17.74l-15.49,16.84h-10.03z" stroke="#000064" stroke-width="10" fill="none"/>
<path fill-opacity="0.3" d="M236.03,308.41l-1.72,3.71-7.78,16.85-3.06,6.62c-2.91,1.64-5.67,3.57-8,6.1-6.46,6.99-9.47,16.59-9.47,27.62,0,9.31,2.28,17.68,7.12,24.38l0.04,0.03c6.25,8.5,16.45,12.81,28.25,12.81,7.55,0,14.45-1.78,20.06-5.65,5.56-3.85,9.61-9.57,12-16.38l2.53-7.16-1.38-0.25,0.07-3.53c0.24-13.05-2.36-23.79-8.97-31.34-4.14-4.73-9.57-7.82-15.66-9.5l12.5-13.6,9.88-10.71h-36.41zm-2.75,36.59c-2.2,1.01-4.2,2.42-5.81,4.22-3.48,3.88-5.02,9.15-4.97,14.59l0.03,3.22h-0.47l0.16,6.56c0.14,5.78,1.85,11.19,5.56,15.16,1.5,1.6,3.33,2.86,5.28,3.84-4.13-1.16-7.22-3.21-9.59-6.43-3.04-4.2-4.66-9.56-4.66-16.85,0-8.8,2.16-14.7,6.07-18.93,2.5-2.72,5.24-4.41,8.4-5.38zm15.94,0.59c0.6,0.25,1.1,0.64,1.66,0.94-0.53-0.36-1.1-0.63-1.66-0.94zm1.72,0.97c1.9,1.05,3.63,2.35,5.15,4.1,2.93,3.34,4.95,8.71,5.57,16.37h-2.6l-0.09-3.44c-0.14-5.6-1.66-10.85-5.28-14.68-0.83-0.88-1.77-1.66-2.75-2.35zm-10.13,9.56c1.43,0.01,2.18,0.34,3.13,1.22h-6.44c0.91-0.86,1.7-1.21,3.31-1.22zm-3.78,23.72h7.75c-0.38,0.77-0.75,1.3-0.94,1.44-0.49,0.4-0.93,0.63-2.25,0.63-2.11,0-3.11-0.43-4.47-1.88-0.04-0.04-0.05-0.14-0.09-0.19zm18.53,7.07l1.97,0.31c-1,1.18-2.09,2.26-3.34,3.12-1.14,0.79-2.44,1.38-3.85,1.91,0.51-0.31,1.04-0.6,1.5-0.97,1.54-1.23,2.75-2.72,3.72-4.37z" fill="#000"/>
<path d="M247.4,375.48l15.77,2.64c-2.02,5.79-5.23,10.19-9.6,13.21-4.38,3.02-9.85,4.54-16.42,4.54-10.4,0-18.1-3.4-23.09-10.2-3.95-5.44-5.92-12.32-5.92-20.61,0-9.92,2.59-17.68,7.78-23.3,5.18-5.61,11.73-8.42,19.65-8.42,8.9,0,15.92,2.94,21.07,8.82,5.14,5.88,7.6,14.88,7.38,27.01h-39.66c0.12,4.69,1.39,8.34,3.83,10.95,2.45,2.61,5.49,3.92,9.13,3.92,2.48,0,4.56-0.68,6.25-2.03s2.97-3.53,3.83-6.53zm0.9-16c-0.11-4.58-1.29-8.07-3.55-10.45-2.25-2.38-4.99-3.58-8.22-3.58-3.45,0-6.31,1.26-8.56,3.78-2.25,2.51-3.36,5.93-3.32,10.25h23.65zm-20.22-32.11l7.78-16.84h17.74l-15.49,16.84h-10.03z" fill="url(#l)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M25.844,80.688c-3.427,0.201-6.099,1.831-7.969,3.968-0.97,1.099-1.8,2.437-2.344,4-2.276,0.558-4.235,1.611-5.656,3.032l-0.0312-0.032c-0.1083,0.101-0.2094,0.206-0.3126,0.313-0.0412,0.042-0.0845,0.081-0.125,0.125l-0.1562,0.156-0.125,0.156c-1.8144,2.203-3.1928,5.222-2.5625,9.034,0.4099,2.47,1.6306,4.23,2.9063,5.62-0.4704-0.02-0.9399-0.09-1.4063-0.09h-14.75c-0.0153-1.66-0.0326-3.43-0.0937-6.13v-2.871c0.0081-0.22,0.0081-0.218,0-0.438v-0.219c-0.2142-2.996-1.4385-6.385-4.5628-8.781-2.742-2.103-5.854-2.492-8.312-2.406v-0.063h-0.313c-2.23,0.081-5.351,0.632-8,2.782-3.122,2.534-4.187,5.874-4.187,9.125-0.004,0.146-0.004,0.135,0,0.281v0.188c0.148,2.732,0.27,5.532,0.344,8.342,0.001,0.07-0.002,0.12,0,0.19h-17.563c-2.952,0-6.189,0.93-8.719,3.78-2.285,2.57-2.885,5.66-2.968,7.91v0.03c-0.077,2.15,0.238,5.34,2.343,8.12,2.478,3.27,5.856,4.32,9,4.31h15.875c-1.561,6.33-4.261,11.82-8.281,16.69-3.494,4.25-8.049,8.02-13.844,11.28-2.61,1.47-5.285,3.91-6.437,7.66-0.904,2.94-0.651,5.87,0.594,8.59,0.075,0.17,0.135,0.34,0.218,0.5,0.133,0.32,0.25,0.63,0.25,0.63l0.063,0.09c0.007,0.01,0.024,0.02,0.031,0.03l0.031,0.07c-0.541-0.91-0.521-0.79,0.157,0.28l3.093,4.87,1.563-0.78c0.986,0.54,1.933,1.15,3.062,1.41,3.446,0.79,6.763,0.03,9.406-1.38l0.219-0.12,0.125-0.1c0.021-0.01,0.042-0.02,0.063-0.03,0.111-0.07,0.235-0.14,0.343-0.22v-0.03c10.4-6.22,18.697-14.33,24.75-24.03h0.376l1.812-3.34c3.595-6.66,6.063-14.07,7.6875-22.07h9.5625c-0.44,5.74-0.9,11.66-1.1562,14.1v0.09l-0.03,0.22c-0.5064,5.19-1.1158,9.15-1.5625,10.94-0.2162,0.78-0.4454,1.14-0.625,1.47h-6.4688c-2.9742,0-6.5122,0.82-9.3122,3.62-2.488,2.49-3.5,5.52-3.5,8.63-0.011,0.25-0.011,0.24,0,0.5v0.25c0.061,0.73,0.386,1.33,0.562,2l-1.344,1.34,4.532,4.53c1.718,1.72,1.831,1.74,0.187,0.16-0.202-0.2-0.188-0.18,0.063,0.06,2.71,2.56,6.053,3.5,9.156,3.5l7.2812,0.13h0.1876c6.6615,0,12.942-2.73,17.25-7.57h0.719l1.812-3.15,0.031-0.03c2.821-4.91,4.094-11.02,5.313-19.07l0.156-0.31,0.125-1.06c0.479-4.07,0.943-9.08,1.406-15.09,0.467-5.21,0.696-8.98,0.781-11.72,0.104-1.87-0.192-3.7-0.687-5.5,2.84,0.2,5.131-0.73,7.031-2l0.032,0.06c0.073-0.04,0.146-0.06,0.218-0.1l0.688-0.37,0.625-0.53c1.368-1.22,2.599-2.87,3.343-4.94,1.367-0.35,2.474-0.98,3.532-1.69l0.031,0.07c0.073-0.04,0.146-0.06,0.219-0.1l0.687-0.37,0.625-0.53c2.042-1.82,3.909-4.57,4.032-8.32,0.116-3.585-1.544-6.502-3.188-8.34l-0.062-0.062-0.094-0.094c-0.072-0.075-0.144-0.147-0.219-0.219-1.61-1.691-2.862-2.91-4.094-4.063l-0.093-0.093-0.094-0.094c-1.897-1.724-3.728-3.203-5.625-4.469-1.827-1.279-4.511-2.402-7.625-2.218zm-17.75,37.752c0.1539,0.02,0.9063,0.12,0.9062,0.12-0.0002,0,0.3432,0.11,0.625,0.19-0.3653-0.07-1.4337-0.29-1.5312-0.31zm-4.2188,1.34h0.1875c-0.3096,0.23-0.3931,0.27-0.625,0.44,0.0494-0.07,0.25-0.35,0.25-0.34,0,0,0.1622-0.09,0.1875-0.1zm8.063,0.78c0.02,0.01,0.042,0.02,0.062,0.03l-0.625,0.53,0.563-0.56zm0.843,0.53c0.027,0.03,0.037,0.07,0.063,0.1l-0.938,0.65,0.875-0.75zm0.5,0.69c0.093,0.16,0.184,0.31,0.25,0.5-0.048-0.08-0.113-0.26-0.25-0.5zm-11.594,1.03c-0.0233,0.11-0.0516,0.24-0.0937,0.44,0.0065-0.07,0.0312-0.34,0.0312-0.34s0.0587-0.09,0.0625-0.1zm11.906,4.28c-0.003,0.06-0.028,0.16-0.032,0.22-0.137,0.23-1,1.72-1,1.72,0.001,0-0.369,0.28-0.593,0.44,0.331-0.49,1.323-1.94,1.625-2.38zm-10.938,1.6c0.1258,0.16,0.316,0.33,0.4688,0.5l-0.0625,0.06c-0.0913-0.1-0.2038-0.23-0.25-0.28-0.1353-0.16-0.1402-0.2-0.1563-0.22-0.004,0-0.0291-0.03-0.0312-0.03l0.0312-0.03zm0.6876,0.75c0.1283,0.12,0.1857,0.25,0.3437,0.37-0.1548-0.12-0.3158-0.25-0.4063-0.34l0.0626-0.03zm0.5312,0.53c0.2412,0.19,0.5718,0.42,1.25,0.72-0.1829-0.08-0.3407-0.14-0.5938-0.28-0.0098-0.01-0.0212-0.03-0.0312-0.03,0.0002,0-0.4694-0.29-0.625-0.41zm7.031,0.25c-0.319,0.23-0.75,0.53-0.75,0.53s-0.3648,0.06-0.531,0.09c0.399-0.19,0.73-0.35,1.281-0.62z" fill="#000"/>
<path d="M21.968,82.795c-1.392,0.082-2.666,0.824-3.531,1.812-0.889,1.008-1.555,2.502-1.312,4,0.242,1.499,1.174,2.603,2.218,3.438,1.879,1.503,3.31,2.692,4.219,3.531,1.214,1.143,2.159,2.174,2.906,3.125,0.021,0.032,0.041,0.063,0.063,0.094,0.933,1.088,2.154,1.985,3.687,2.185,1.534,0.21,3.014-0.43,4.094-1.341,0.021-0.01,0.042-0.021,0.063-0.032,1.018-0.905,1.857-2.235,1.906-3.75,0.049-1.514-0.646-2.818-1.563-3.843-0.02-0.021-0.041-0.042-0.062-0.063-1.559-1.636-2.918-2.965-4.094-4.062-0.01-0.011-0.021-0.021-0.031-0.032-1.741-1.582-3.356-2.893-4.875-3.906-1.063-0.744-2.266-1.24-3.688-1.156zm-45.968,5.406c-1.526,0.055-2.976,0.36-4.188,1.344-1.228,0.997-1.844,2.662-1.844,4.156-0.001,0.042-0.001,0.084,0,0.125,0.151,2.794,0.269,5.661,0.344,8.534,0.055,2.48,0.026,4.61,0,6.75h-23.969c-1.377,0-2.914,0.51-3.906,1.62-0.992,1.12-1.323,2.52-1.375,3.91-0.05,1.4,0.145,2.82,1.063,4.03,0.917,1.21,2.503,1.78,3.875,1.78h23.5c-1.118,10.52-4.697,19.55-10.969,27.16-4.045,4.91-9.235,9.18-15.625,12.78-1.585,0.89-2.932,2.22-3.469,3.97-0.472,1.53-0.261,3.32,0.563,4.72,0.003,0-0.004,0.02,0,0.03,0.023,0.04,0.037,0.08,0.062,0.12l0.063-0.03c0.814,1.38,2.219,2.37,3.718,2.72,1.664,0.38,3.407,0.04,4.938-0.78,0.032-0.02,0.063-0.04,0.094-0.06,10.928-6.45,19.303-14.87,24.937-25.19h0.031c3.976-7.36,6.576-15.91,8-25.44h20.688c0.9846,0,0.952,0.2,0.875,0.09,0.0205,0.03,0.0413,0.05,0.0625,0.07-0.1075-0.15,0.1515,0.24,0.0937,1.28-0.6844,9.66-1.2515,16.24-1.625,19.75v0.03c-0.5275,5.5-1.1278,9.6-1.75,12.09-0.76,2.76-1.7441,4.35-2.5937,5.07-0.021,0.01-0.0418,0.02-0.0625,0.03-0.8088,0.72-2.0336,1.22-4.125,1.22h-6.5309c-1.748,0-3.534,0.5-4.782,1.75-1.062,1.06-1.625,2.63-1.625,4.09-0.003,0.07-0.003,0.15,0,0.22,0.123,1.47,0.862,2.86,1.907,3.84l-0.032,0.03c0.027,0.03,0.067,0.04,0.094,0.07,0.011,0.01,0.021,0.02,0.031,0.03,1.262,1.19,3.032,1.75,4.75,1.75l7.4066,0.12h0.0313c6.2648,0,11.418-2.61,14.281-7.53h0.0313c2.1206-3.69,3.5126-9.5,4.7496-17.94,0.011-0.02,0.022-0.04,0.032-0.06,0.462-3.93,0.916-8.88,1.375-14.84,0.459-5.13,0.702-8.88,0.781-11.41,0.184-3.32-0.967-6.4-3.406-8.44v-0.03c-2.262-1.85-5.2882-2.62-8.7191-2.62h-21.188c0.036-1.59,0.094-3.12,0.094-4.88,0-1.65-0.049-4.14-0.125-7.498v-3.031c0.002-0.062,0.002-0.125,0-0.187-0.11-1.546-0.755-3.154-2.062-4.157-1.29-0.989-2.871-1.245-4.438-1.156h-0.062zm37.406,2.094c-1.368,0.121-2.581,0.835-3.4689,1.781-0.0431,0.04-0.0848,0.082-0.125,0.125-0.8348,1.014-1.4285,2.481-1.1875,3.938,0.2411,1.457,1.1663,2.537,2.1874,3.343,1.792,1.498,3.121,2.698,4.156,3.658,0.011,0.01,0.021,0.02,0.032,0.03,1.298,1.15,2.297,2.17,3,3.06,0.02,0.03,0.041,0.07,0.062,0.1,0.934,1.09,2.159,2,3.688,2.21,1.529,0.22,3.029-0.42,4.125-1.34,0.021-0.01,0.041-0.02,0.062-0.03,1.026-0.91,1.875-2.27,1.906-3.78,0.032-1.52-0.683-2.8-1.593-3.814-0.021-0.021-0.042-0.042-0.063-0.062-1.547-1.625-2.9-2.98-4.094-4.094-0.01-0.011-0.02-0.021-0.031-0.031-1.727-1.57-3.347-2.912-4.937-4-1.059-0.725-2.349-1.215-3.719-1.094z" stroke="#3c3c00" stroke-width="10" fill="none"/>
<path d="M21.968,82.795c-1.392,0.082-2.666,0.824-3.531,1.812-0.889,1.008-1.555,2.502-1.312,4,0.242,1.499,1.174,2.603,2.218,3.438,1.879,1.503,3.31,2.692,4.219,3.531,1.214,1.143,2.159,2.174,2.906,3.125,0.021,0.032,0.041,0.063,0.063,0.094,0.933,1.088,2.154,1.985,3.687,2.185,1.534,0.21,3.014-0.43,4.094-1.341,0.021-0.01,0.042-0.021,0.063-0.032,1.018-0.905,1.857-2.235,1.906-3.75,0.049-1.514-0.646-2.818-1.563-3.843-0.02-0.021-0.041-0.042-0.062-0.063-1.559-1.636-2.918-2.965-4.094-4.062-0.01-0.011-0.021-0.021-0.031-0.032-1.741-1.582-3.356-2.893-4.875-3.906-1.063-0.744-2.266-1.24-3.688-1.156zm-45.968,5.406c-1.526,0.055-2.976,0.36-4.188,1.344-1.228,0.997-1.844,2.662-1.844,4.156-0.001,0.042-0.001,0.084,0,0.125,0.151,2.794,0.269,5.661,0.344,8.534,0.055,2.48,0.026,4.61,0,6.75h-23.969c-1.377,0-2.914,0.51-3.906,1.62-0.992,1.12-1.323,2.52-1.375,3.91-0.05,1.4,0.145,2.82,1.063,4.03,0.917,1.21,2.503,1.78,3.875,1.78h23.5c-1.118,10.52-4.697,19.55-10.969,27.16-4.045,4.91-9.235,9.18-15.625,12.78-1.585,0.89-2.932,2.22-3.469,3.97-0.472,1.53-0.261,3.32,0.563,4.72,0.003,0-0.004,0.02,0,0.03,0.023,0.04,0.037,0.08,0.062,0.12l0.063-0.03c0.814,1.38,2.219,2.37,3.718,2.72,1.664,0.38,3.407,0.04,4.938-0.78,0.032-0.02,0.063-0.04,0.094-0.06,10.928-6.45,19.303-14.87,24.937-25.19h0.031c3.976-7.36,6.576-15.91,8-25.44h20.688c0.9846,0,0.952,0.2,0.875,0.09,0.0205,0.03,0.0413,0.05,0.0625,0.07-0.1075-0.15,0.1515,0.24,0.0937,1.28-0.6844,9.66-1.2515,16.24-1.625,19.75v0.03c-0.5275,5.5-1.1278,9.6-1.75,12.09-0.76,2.76-1.7441,4.35-2.5937,5.07-0.021,0.01-0.0418,0.02-0.0625,0.03-0.8088,0.72-2.0336,1.22-4.125,1.22h-6.5309c-1.748,0-3.534,0.5-4.782,1.75-1.062,1.06-1.625,2.63-1.625,4.09-0.003,0.07-0.003,0.15,0,0.22,0.123,1.47,0.862,2.86,1.907,3.84l-0.032,0.03c0.027,0.03,0.067,0.04,0.094,0.07,0.011,0.01,0.021,0.02,0.031,0.03,1.262,1.19,3.032,1.75,4.75,1.75l7.4066,0.12h0.0313c6.2648,0,11.418-2.61,14.281-7.53h0.0313c2.1206-3.69,3.5126-9.5,4.7496-17.94,0.011-0.02,0.022-0.04,0.032-0.06,0.462-3.93,0.916-8.88,1.375-14.84,0.459-5.13,0.702-8.88,0.781-11.41,0.184-3.32-0.967-6.4-3.406-8.44v-0.03c-2.262-1.85-5.2882-2.62-8.7191-2.62h-21.188c0.036-1.59,0.094-3.12,0.094-4.88,0-1.65-0.049-4.14-0.125-7.498v-3.031c0.002-0.062,0.002-0.125,0-0.187-0.11-1.546-0.755-3.154-2.062-4.157-1.29-0.989-2.871-1.245-4.438-1.156h-0.062zm37.406,2.094c-1.368,0.121-2.581,0.835-3.4689,1.781-0.0431,0.04-0.0848,0.082-0.125,0.125-0.8348,1.014-1.4285,2.481-1.1875,3.938,0.2411,1.457,1.1663,2.537,2.1874,3.343,1.792,1.498,3.121,2.698,4.156,3.658,0.011,0.01,0.021,0.02,0.032,0.03,1.298,1.15,2.297,2.17,3,3.06,0.02,0.03,0.041,0.07,0.062,0.1,0.934,1.09,2.159,2,3.688,2.21,1.529,0.22,3.029-0.42,4.125-1.34,0.021-0.01,0.041-0.02,0.062-0.03,1.026-0.91,1.875-2.27,1.906-3.78,0.032-1.52-0.683-2.8-1.593-3.814-0.021-0.021-0.042-0.042-0.063-0.062-1.547-1.625-2.9-2.98-4.094-4.094-0.01-0.011-0.02-0.021-0.031-0.031-1.727-1.57-3.347-2.912-4.937-4-1.059-0.725-2.349-1.215-3.719-1.094z" fill="url(#m)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M-61.025,286.89c-3.782,0-7.29,2.17-8.968,5.56-2.725,5.45-4,11.45-4,17.66,0.002,2.9,1.311,5.59,3.437,7.47l1,6.03c1.254,7.88,2.268,14.86,3.125,21.22,0.957,7.16,1.294,11.61,1.406,14.46-0.503,0.33-0.753,0.54-2.125,1.1-3.979,1.6-8.262,2.44-13.218,2.44-4.428,0-6.878-0.95-7.75-1.57-1.089-0.76-0.594,0.48-0.594-1.93,0-1.31,0.182-2.78,0.656-4.47,0.403-1.41,1.087-3.24,2.094-5.41,0.778-1.65,1.762-3.55,3.031-5.84,2.602-4.68,0.949-10.72-3.656-13.44l-1.688-1.03c-2.324-1.4-5.099-1.77-7.718-1.06-2.619,0.7-4.847,2.43-6.157,4.81-1.99,3.6-3.62,6.73-4.91,9.56-1.75,3.83-3.1,7.47-4.03,10.97-1.11,4.12-1.68,8.22-1.68,12.28,0,7.05,2.85,13.72,7.9,18.47v0.62l2.81,1.88c6.143,4.11,13.616,5.53,21.785,5.53,6.2,0,11.614-0.55,16.532-1.75h0.437l0.656-0.19,0.125-0.03,0.063-0.03c6.188-1.71,11.792-4.84,15.781-9.78l0.031-0.03c4.435-5.56,6.282-12.64,6.282-20.03,0-4.72-0.518-10.68-1.407-18.38-0.561-4.86-1.368-11.12-2.5-18.75v-0.03c-0.239-1.59-0.264-1.84-0.375-2.59,0.733-1.01,1.337-2.11,1.657-3.35,0.59-2.28,1.016-4.12,1.281-5.81,0.335-2.15,0.406-4.35,0.406-7.09-0.003-3.79-2.172-7.29-5.562-8.97-0.744-0.37-1.617-0.81-2.657-1.31-0.48-0.24-0.886-0.39-1.343-0.6-1.431-3.8-4.989-6.58-9.188-6.59h-0.969zm2.5,11.25h0.032c-0.007,0.01-0.025,0.02-0.032,0.03v-0.03zm1,6.37c0.79,0.35,1.549,0.68,2.407,1.1h0.031v0.03c0.334,0.16,0.597,0.28,0.906,0.44-0.038,1.29-0.061,2.76-0.156,3.37-0.103,0.66-0.436,1.97-0.719,3.16l-3.187,1.09-0.157-0.97-0.531-3.09-2.094-1.13c0.071-1.36,0.191-2.7,0.438-3.97l3.062-0.03zm4.938,2.35h0.031v0.03c-0.013-0.01-0.018-0.03-0.031-0.03zm-47.344,59.28c1.065,2.28,2.593,4.26,4.438,5.56,3.942,2.78,8.992,3.94,15.125,3.94,6.437,0,12.488-1.14,18.031-3.38,3.08-1.24,5.327-2.45,7.312-4.37,0.407-0.4,0.767-1.03,1.157-1.53-0.585,2.41-1.505,4.43-2.782,6.03-1.939,2.4-4.919,4.22-9.062,5.4l-0.188,0.07c-3.645,0.99-8.417,1.53-14.375,1.53-6.53,0-11.23-1.15-14.437-3.22l-0.219-0.16c-3.183-2.14-4.887-5.42-5-9.87z" fill="#000"/>
<path d="M-63.656,295.12c-1.366,0.01-2.613,0.78-3.219,2-2.252,4.51-3.344,9.49-3.344,14.82,0.001,1.34,0.748,2.57,1.938,3.18l0.937,0.5,1.469,8.76c1.262,7.93,2.292,15.03,3.156,21.43,1.113,8.33,1.656,14.29,1.657,17.41-0.001,0.55-0.107,1.01-1.032,1.9-0.924,0.9-2.69,1.99-5.281,3.04-4.761,1.91-9.928,2.9-15.625,2.9-5.28,0-9.03-1.05-11.438-2.75-2.331-1.64-3.312-3.57-3.312-7.15,0-1.96,0.297-4.01,0.906-6.19,0.512-1.79,1.306-3.94,2.438-6.38,0.868-1.84,1.945-3.88,3.25-6.25,0.948-1.7,0.365-3.85-1.313-4.84l-1.719-1.03c-0.836-0.5-1.842-0.65-2.785-0.39-0.943,0.25-1.743,0.88-2.215,1.73-1.942,3.52-3.502,6.53-4.692,9.13-1.62,3.55-2.83,6.87-3.65,9.97-0.98,3.63-1.47,7.17-1.47,10.62,0,6.5,2.76,12.18,7.88,15.63-0.01,0.01-0.01,0.02,0,0.03,4.669,3.13,10.796,4.44,18.214,4.43,6.324,0,11.633-0.54,16.062-1.74h0.032c5.224-1.45,9.549-3.97,12.531-7.66,3.326-4.17,4.875-9.67,4.875-16.03,0-4.25-0.468-10.07-1.344-17.66-0.55-4.76-1.375-10.95-2.5-18.53-0.547-3.63-0.617-4.16-0.875-6,1.142-0.39,2.01-1.33,2.313-2.5,0.563-2.18,0.948-3.89,1.156-5.22,0.239-1.53,0.312-3.47,0.312-6.09-0.001-1.37-0.776-2.62-2-3.22-0.754-0.38-1.612-0.81-2.594-1.28-1.378-0.67-2.668-1.25-3.874-1.75h-0.032c0.001-0.03-0.201-0.54-0.312-1.6-0.193-1.82-1.728-3.21-3.563-3.22h-0.937z" transform="translate(-1.63, -6.1)" stroke="#510000" stroke-width="10" fill="none"/>
<path transform="translate(-1.63, -6.1)" d="M-63.656,295.12c-1.366,0.01-2.613,0.78-3.219,2-2.252,4.51-3.344,9.49-3.344,14.82,0.001,1.34,0.748,2.57,1.938,3.18l0.937,0.5,1.469,8.76c1.262,7.93,2.292,15.03,3.156,21.43,1.113,8.33,1.656,14.29,1.657,17.41-0.001,0.55-0.107,1.01-1.032,1.9-0.924,0.9-2.69,1.99-5.281,3.04-4.761,1.91-9.928,2.9-15.625,2.9-5.28,0-9.03-1.05-11.438-2.75-2.331-1.64-3.312-3.57-3.312-7.15,0-1.96,0.297-4.01,0.906-6.19,0.512-1.79,1.306-3.94,2.438-6.38,0.868-1.84,1.945-3.88,3.25-6.25,0.948-1.7,0.365-3.85-1.313-4.84l-1.719-1.03c-0.836-0.5-1.842-0.65-2.785-0.39-0.943,0.25-1.743,0.88-2.215,1.73-1.942,3.52-3.502,6.53-4.692,9.13-1.62,3.55-2.83,6.87-3.65,9.97-0.98,3.63-1.47,7.17-1.47,10.62,0,6.5,2.76,12.18,7.88,15.63-0.01,0.01-0.01,0.02,0,0.03,4.669,3.13,10.796,4.44,18.214,4.43,6.324,0,11.633-0.54,16.062-1.74h0.032c5.224-1.45,9.549-3.97,12.531-7.66,3.326-4.17,4.875-9.67,4.875-16.03,0-4.25-0.468-10.07-1.344-17.66-0.55-4.76-1.375-10.95-2.5-18.53-0.547-3.63-0.617-4.16-0.875-6,1.142-0.39,2.01-1.33,2.313-2.5,0.563-2.18,0.948-3.89,1.156-5.22,0.239-1.53,0.312-3.47,0.312-6.09-0.001-1.37-0.776-2.62-2-3.22-0.754-0.38-1.612-0.81-2.594-1.28-1.378-0.67-2.668-1.25-3.874-1.75h-0.032c0.001-0.03-0.201-0.54-0.312-1.6-0.193-1.82-1.728-3.21-3.563-3.22h-0.937z" fill="url(#n)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M84.844,406.06c-1.134,0.12-2.236,0.56-3.25,1.25-2.029,1.38-3.196,3.74-3.063,6.19,0.174,5.03,0.324,11.84,0.5,18.62h-3.937l-1.406-1.24c0.945-0.89,1.658-2.03,2-3.32,0.671-2.53-0.141-5.28-2.094-7.03l-0.094-0.09-0.125-0.1-6.156-5.06c-1.462-1.3-3.212-1.76-5.157-1.56-0.608-0.95-1.353-1.81-2.374-2.38l-7.032-4.25c-1.169-0.73-2.433-1.03-3.812-1h-0.032c-3.379,0.1-6.28,2.69-6.75,6.03,0.078-0.54-0.176,0.75-0.812,2.35-0.636,1.59-1.628,3.76-2.906,6.44-2.398,5.01-6.437,11.81-12.094,20.06-1.892,2.72-1.551,6.46,0.75,8.84l0.031,0.03,0.031,0.04,1.094,1.09c1.083,1.09,2.488,1.66,3.938,1.87-0.042,8.6-0.07,18.19-0.219,22.94v0.03c-0.029,1.03,0.291,1.99,0.687,2.91-0.999,3.22-2.13,5.28-2.093,5.25-1.466,1.29-3.047,2.89-3.907,5.47-1.038,3.11-0.225,6.71,2.126,9.06,2.968,2.97,8.227,3.76,11.937,1.91,1.539-0.77,2.807-1.85,3.875-3.07,2.082,1.47,4.625,1.82,6.625,1.54,1.928-0.28,3.638-1.36,5.094-2.69,0.346,0.2,0.676,0.45,1.031,0.59-0.108,1.44,0.166,2.93,0.969,4.25l0.062,0.09,0.063,0.1,0.718,1.09c2.045,3.15,6.379,4.08,9.563,2.1,7.964-4.74,14.225-11.13,18.937-18.88v2.44c0,3.22,0.735,6.66,3.094,9.47,2.405,2.86,5.991,4.2,9.282,4.4h0.252c0.12,0.01,0.23,0.01,0.34,0h11.06c5.8,0,11.08-4.17,13.29-9.31,0.95-2.06,0.78-4.28-0.35-6.25-0.64-1.12-1.61-1.95-2.72-2.56-0.19-2.55-0.31-5.72-0.31-9.84,0.02-1.89-0.73-3.64-2.06-4.97-1.34-1.34-3.18-2.09-5.06-2.07h-1.72c-2.79-0.03-5.22,1.65-6.35,4.07v-22.94h12.13c2.86,0.05,5.41-1.69,6.5-4.35,1.09-2.65,0.44-5.73-1.63-7.71l-6.03-6.07c-0.43-0.43-0.98-0.69-1.5-1-0.14-2.99-1.36-5.84-3.06-7.96-3-3.75-7.01-5.8-12.69-8.38-0.143-0.07-0.294-0.04-0.436-0.09-0.658-0.82-1.432-1.58-2.406-2.03l-0.126-0.07-0.124-0.03-8.688-3.62c-1.113-0.52-2.272-0.72-3.406-0.6zm-5.813,42.6c-0.196,5.17-0.706,10.05-1.75,14.46-0.072-4-0.164-8.36-0.187-14.12,0.509-0.04,0.999-0.09,1.5-0.25l0.062-0.03h0.063c0.085-0.03,0.225-0.04,0.312-0.06z" fill="#000"/>
<path d="M44.734,408.22c-0.301,0.01-0.552,0.23-0.593,0.53-0.21,1.47-1.637,5.13-4.282,10.66-2.628,5.5-6.819,12.48-12.593,20.91-0.169,0.24-0.143,0.56,0.062,0.78l1.094,1.09c0.229,0.23,0.598,0.25,0.844,0.03,1.939-1.74,3.586-3.5,5.062-5.25-0.009,15.45-0.083,27.42-0.312,34.75-0.007,0.22,0.104,0.43,0.291,0.54,0.187,0.12,0.421,0.13,0.615,0.02l4.687-2.53c0.204-0.1,0.336-0.3,0.344-0.53v-2.62h20.438v3.97c-0.06-0.03-0.097-0.07-0.157-0.1-0.286-0.15-0.641-0.05-0.812,0.22l-0.719,1.09c-0.152,0.24-0.126,0.55,0.063,0.75,1.673,1.92,2.84,3.45,3.531,4.6,0.687,1.14,1.345,2.7,1.937,4.59,0.325,1.04,0.759,1.82,1.469,2.22s1.604,0.24,2.375-0.25c0.324-0.21,0.57-0.47,0.813-0.75-2.695,3.53-5.749,6.78-9.313,9.69-0.253,0.19-0.32,0.54-0.156,0.81l0.719,1.09c0.183,0.29,0.558,0.37,0.843,0.19,17.903-10.64,26.682-29.9,26.344-57.4h2.375v46.37c0,2.26,0.507,4.08,1.594,5.38,1.086,1.29,2.737,1.99,4.781,2.12h11.252c3.35,0,5.9-1.92,7.4-5.44,0.09-0.18,0.08-0.39-0.02-0.56-0.1-0.18-0.28-0.29-0.48-0.31-0.95-0.11-1.59-0.47-2.09-1.13s-0.82-1.66-0.94-3.03c-0.24-2.87-0.37-6.56-0.37-11.12,0-0.17-0.07-0.33-0.18-0.45-0.12-0.12-0.28-0.18-0.45-0.18h-1.81c-0.33,0-0.61,0.26-0.62,0.6-0.25,5.29-0.55,9.38-0.91,12.25-0.17,1.37-0.53,2.34-1,2.93s-1,0.85-1.81,0.85h-4.03c-1.827,0.22-2.995-0.07-3.661-0.78-0.666-0.72-0.949-2.02-0.718-3.97,0.001-0.02,0.001-0.05,0-0.07v-43.46h18.529c0.26,0,0.49-0.15,0.58-0.39,0.1-0.23,0.04-0.5-0.14-0.68l-6.13-6.15c-0.11-0.12-0.27-0.19-0.43-0.19-0.17,0-0.32,0.07-0.44,0.19l-4.16,4.15h-16.122v-19.31l2.969-1.97c0.179-0.13,0.278-0.34,0.258-0.56-0.019-0.22-0.153-0.41-0.352-0.5l-8.687-3.63c-0.197-0.09-0.427-0.07-0.606,0.05s-0.281,0.33-0.269,0.55c0.235,6.82,0.482,15.34,0.718,25.37h-12c-0.238,0-0.457,0.13-0.562,0.35l-5.188-4.53c-0.128-0.11-0.292-0.16-0.457-0.14-0.164,0.02-0.315,0.1-0.418,0.23l-2.718,3.37h-7.094c2.095-2.77,3.962-5.22,5.312-6.84,1.548-1.86,2.694-2.98,3.094-3.22,1.15-0.69,2.306-1.15,3.438-1.37,0.226-0.05,0.406-0.22,0.465-0.45,0.06-0.22-0.012-0.46-0.184-0.62l-6.156-5.06c-0.13-0.11-0.302-0.17-0.474-0.15-0.173,0.02-0.33,0.11-0.433,0.25l-2.343,3h-8.688c0.965-1.64,1.886-3,2.688-3.85,0.961-1.02,1.763-1.4,2.343-1.4,0.284,0,0.534-0.19,0.608-0.46s-0.048-0.56-0.295-0.7l-7.219-4.34c-0.103-0.07-0.222-0.1-0.344-0.1zm48.094,5.78c-0.154,0.01-0.299,0.08-0.406,0.19l-1.094,1.09c-0.119,0.13-0.179,0.3-0.168,0.47,0.012,0.17,0.096,0.33,0.231,0.44,3.795,3.08,6.048,6.14,6.843,9.09,0.432,1.61,0.907,2.79,1.5,3.6,0.296,0.4,0.626,0.72,1.036,0.9,0.4,0.19,0.88,0.23,1.31,0.1,1.44-0.44,2.42-1.73,2.94-3.53,0.54-1.9-0.04-3.85-1.57-5.75-1.59-1.99-5.003-4.11-10.341-6.53-0.087-0.05-0.184-0.07-0.281-0.07zm-47.75,8.13h9.531l-5.562,11.4h-8.094l-2.562-1.71c2.227-2.93,4.462-6.15,6.687-9.69zm24.438,13.81l2.281,2.28c0.162,0.17,0.404,0.23,0.625,0.16,2.098-0.7,4.468-1.06,7.062-1.06h1.907c0.622,18.59-3.482,33.69-12.282,45.37,0.239-0.33,0.435-0.72,0.563-1.12,0.278-0.89,0.285-1.89,0.094-3.04-0.221-1.32-1.188-2.59-2.782-3.9-1.344-1.11-3.247-2.29-5.531-3.5l4.875-2.06c0.236-0.11,0.386-0.34,0.375-0.6-0.238-6.18-0.342-16.38-0.344-30.37l2.969-2c0.072-0.04,0.136-0.1,0.188-0.16zm-29.563,0.66h7.438v11.75h-7.438v-11.75zm13.031,0h7.407v11.75h-7.407v-11.75zm-13.031,14.81h7.438v12.12h-7.438v-12.12zm13.031,0h7.407v12.12h-7.407v-12.12zm-1.812,20.09c-0.089,0.02-0.175,0.05-0.25,0.1l-1.094,0.72c-0.282,0.18-0.365,0.55-0.187,0.84,0.953,1.67,1.786,3.33,2.5,5,0.7,1.63,1.238,3.49,1.593,5.62,0.193,1.16,0.497,2.05,1.157,2.57,0.659,0.52,1.595,0.48,2.437,0.06,1.68-0.84,2.471-2.67,2.344-5.09-0.072-1.37-0.845-2.79-2.188-4.38s-3.29-3.37-5.843-5.31c-0.133-0.11-0.302-0.15-0.469-0.13zm-8.656,1.44c-0.144,0.02-0.276,0.08-0.375,0.19l-0.719,0.72c-0.167,0.17-0.216,0.43-0.125,0.65,0.466,1.17,0.894,2.59,1.25,4.25,0.345,1.62,0.431,3.56,0.312,5.82-0.064,1.22,0.113,2.2,0.657,2.9,0.543,0.7,1.46,0.95,2.437,0.81,0.993-0.14,1.811-0.67,2.344-1.5,0.532-0.82,0.811-1.91,0.875-3.25,0.137-2.88-2.012-6.26-6.156-10.4-0.13-0.14-0.314-0.21-0.5-0.19zm-6.875,0.72c-0.256,0.04-0.455,0.24-0.5,0.5-1.192,5.24-2.85,8.69-4.719,10.34-1.067,0.94-1.765,1.8-2.063,2.69s-0.075,1.86,0.594,2.53c1.23,1.23,2.914,1.5,4.531,0.69,1.597-0.8,2.767-2.25,3.532-4.16,0.816-2.04,1.023-5.81,0.781-11.62-0.016-0.27-0.206-0.5-0.469-0.56l-1.437-0.38c-0.081-0.03-0.166-0.04-0.25-0.03z" stroke="#003c00" stroke-width="10" fill="none"/>
<path d="M44.734,408.22c-0.301,0.01-0.552,0.23-0.593,0.53-0.21,1.47-1.637,5.13-4.282,10.66-2.628,5.5-6.819,12.48-12.593,20.91-0.169,0.24-0.143,0.56,0.062,0.78l1.094,1.09c0.229,0.23,0.598,0.25,0.844,0.03,1.939-1.74,3.586-3.5,5.062-5.25-0.009,15.45-0.083,27.42-0.312,34.75-0.007,0.22,0.104,0.43,0.291,0.54,0.187,0.12,0.421,0.13,0.615,0.02l4.687-2.53c0.204-0.1,0.336-0.3,0.344-0.53v-2.62h20.438v3.97c-0.06-0.03-0.097-0.07-0.157-0.1-0.286-0.15-0.641-0.05-0.812,0.22l-0.719,1.09c-0.152,0.24-0.126,0.55,0.063,0.75,1.673,1.92,2.84,3.45,3.531,4.6,0.687,1.14,1.345,2.7,1.937,4.59,0.325,1.04,0.759,1.82,1.469,2.22s1.604,0.24,2.375-0.25c0.324-0.21,0.57-0.47,0.813-0.75-2.695,3.53-5.749,6.78-9.313,9.69-0.253,0.19-0.32,0.54-0.156,0.81l0.719,1.09c0.183,0.29,0.558,0.37,0.843,0.19,17.903-10.64,26.682-29.9,26.344-57.4h2.375v46.37c0,2.26,0.507,4.08,1.594,5.38,1.086,1.29,2.737,1.99,4.781,2.12h11.252c3.35,0,5.9-1.92,7.4-5.44,0.09-0.18,0.08-0.39-0.02-0.56-0.1-0.18-0.28-0.29-0.48-0.31-0.95-0.11-1.59-0.47-2.09-1.13s-0.82-1.66-0.94-3.03c-0.24-2.87-0.37-6.56-0.37-11.12,0-0.17-0.07-0.33-0.18-0.45-0.12-0.12-0.28-0.18-0.45-0.18h-1.81c-0.33,0-0.61,0.26-0.62,0.6-0.25,5.29-0.55,9.38-0.91,12.25-0.17,1.37-0.53,2.34-1,2.93s-1,0.85-1.81,0.85h-4.03c-1.827,0.22-2.995-0.07-3.661-0.78-0.666-0.72-0.949-2.02-0.718-3.97,0.001-0.02,0.001-0.05,0-0.07v-43.46h18.529c0.26,0,0.49-0.15,0.58-0.39,0.1-0.23,0.04-0.5-0.14-0.68l-6.13-6.15c-0.11-0.12-0.27-0.19-0.43-0.19-0.17,0-0.32,0.07-0.44,0.19l-4.16,4.15h-16.122v-19.31l2.969-1.97c0.179-0.13,0.278-0.34,0.258-0.56-0.019-0.22-0.153-0.41-0.352-0.5l-8.687-3.63c-0.197-0.09-0.427-0.07-0.606,0.05s-0.281,0.33-0.269,0.55c0.235,6.82,0.482,15.34,0.718,25.37h-12c-0.238,0-0.457,0.13-0.562,0.35l-5.188-4.53c-0.128-0.11-0.292-0.16-0.457-0.14-0.164,0.02-0.315,0.1-0.418,0.23l-2.718,3.37h-7.094c2.095-2.77,3.962-5.22,5.312-6.84,1.548-1.86,2.694-2.98,3.094-3.22,1.15-0.69,2.306-1.15,3.438-1.37,0.226-0.05,0.406-0.22,0.465-0.45,0.06-0.22-0.012-0.46-0.184-0.62l-6.156-5.06c-0.13-0.11-0.302-0.17-0.474-0.15-0.173,0.02-0.33,0.11-0.433,0.25l-2.343,3h-8.688c0.965-1.64,1.886-3,2.688-3.85,0.961-1.02,1.763-1.4,2.343-1.4,0.284,0,0.534-0.19,0.608-0.46s-0.048-0.56-0.295-0.7l-7.219-4.34c-0.103-0.07-0.222-0.1-0.344-0.1zm48.094,5.78c-0.154,0.01-0.299,0.08-0.406,0.19l-1.094,1.09c-0.119,0.13-0.179,0.3-0.168,0.47,0.012,0.17,0.096,0.33,0.231,0.44,3.795,3.08,6.048,6.14,6.843,9.09,0.432,1.61,0.907,2.79,1.5,3.6,0.296,0.4,0.626,0.72,1.036,0.9,0.4,0.19,0.88,0.23,1.31,0.1,1.44-0.44,2.42-1.73,2.94-3.53,0.54-1.9-0.04-3.85-1.57-5.75-1.59-1.99-5.003-4.11-10.341-6.53-0.087-0.05-0.184-0.07-0.281-0.07zm-47.75,8.13h9.531l-5.562,11.4h-8.094l-2.562-1.71c2.227-2.93,4.462-6.15,6.687-9.69zm24.438,13.81l2.281,2.28c0.162,0.17,0.404,0.23,0.625,0.16,2.098-0.7,4.468-1.06,7.062-1.06h1.907c0.622,18.59-3.482,33.69-12.282,45.37,0.239-0.33,0.435-0.72,0.563-1.12,0.278-0.89,0.285-1.89,0.094-3.04-0.221-1.32-1.188-2.59-2.782-3.9-1.344-1.11-3.247-2.29-5.531-3.5l4.875-2.06c0.236-0.11,0.386-0.34,0.375-0.6-0.238-6.18-0.342-16.38-0.344-30.37l2.969-2c0.072-0.04,0.136-0.1,0.188-0.16zm-29.563,0.66h7.438v11.75h-7.438v-11.75zm13.031,0h7.407v11.75h-7.407v-11.75zm-13.031,14.81h7.438v12.12h-7.438v-12.12zm13.031,0h7.407v12.12h-7.407v-12.12zm-1.812,20.09c-0.089,0.02-0.175,0.05-0.25,0.1l-1.094,0.72c-0.282,0.18-0.365,0.55-0.187,0.84,0.953,1.67,1.786,3.33,2.5,5,0.7,1.63,1.238,3.49,1.593,5.62,0.193,1.16,0.497,2.05,1.157,2.57,0.659,0.52,1.595,0.48,2.437,0.06,1.68-0.84,2.471-2.67,2.344-5.09-0.072-1.37-0.845-2.79-2.188-4.38s-3.29-3.37-5.843-5.31c-0.133-0.11-0.302-0.15-0.469-0.13zm-8.656,1.44c-0.144,0.02-0.276,0.08-0.375,0.19l-0.719,0.72c-0.167,0.17-0.216,0.43-0.125,0.65,0.466,1.17,0.894,2.59,1.25,4.25,0.345,1.62,0.431,3.56,0.312,5.82-0.064,1.22,0.113,2.2,0.657,2.9,0.543,0.7,1.46,0.95,2.437,0.81,0.993-0.14,1.811-0.67,2.344-1.5,0.532-0.82,0.811-1.91,0.875-3.25,0.137-2.88-2.012-6.26-6.156-10.4-0.13-0.14-0.314-0.21-0.5-0.19zm-6.875,0.72c-0.256,0.04-0.455,0.24-0.5,0.5-1.192,5.24-2.85,8.69-4.719,10.34-1.067,0.94-1.765,1.8-2.063,2.69s-0.075,1.86,0.594,2.53c1.23,1.23,2.914,1.5,4.531,0.69,1.597-0.8,2.767-2.25,3.532-4.16,0.816-2.04,1.023-5.81,0.781-11.62-0.016-0.27-0.206-0.5-0.469-0.56l-1.437-0.38c-0.081-0.03-0.166-0.04-0.25-0.03z" fill="url(#o)"/>
</g>
<g transform="matrix(0.6, 0, 0, 0.6, 83.43, -47.62)">
<path fill-opacity="0.3" d="M208.28,99.312c-9.25,0.001-16.95,1.548-22.87,5.718-5.68,4-9.54,10.17-11.6,17.53l-1.87,6.78,6.9,1.25,7.72,1.41c-0.33,0.15-0.77,0.25-1.09,0.41-4.18,2.02-7.7,5.14-10.09,9.06-2.4,3.92-3.57,8.51-3.57,13.22,0,7.06,2.76,13.73,7.81,18.5,5.22,4.91,12.4,7.19,20.29,7.19,4.61,0,9.11-0.88,13.28-2.66,1.26-0.54,2.42-1.31,3.62-2l1.25,3.19h31.69l-4.47-9.19c-1.33-2.73-2.17-5.18-2.59-7.25-0.38-1.87-0.69-5.87-0.69-11.41l0.16-20.12v-0.06c0-7.95-0.41-13.62-3.1-18.54-2.31-4.23-6.15-7.42-10.78-9.65-5.31-2.57-11.79-3.377-20-3.378zm0,12.808c7.2,0.01,12.2,1.02,14.44,2.1,2.92,1.41,4.36,2.86,5.12,4.25,0.39,0.7,1.53,5.34,1.54,12.41l-0.19,20.12v0.06c0,5.95,0.24,10.39,0.97,13.97,0.07,0.36,0.22,0.73,0.31,1.09h-3.41c-0.06-0.22-0.09-0.26-0.15-0.5-0.31-1.08-0.44-1.65-0.75-2.5l-2.6-6.9c0.2-0.49,0.54-0.9,0.69-1.41,0.94-3.07,0.94-5.84,0.94-10.22v-16.43c0-4.38-1.17-8.91-4.69-11.94-3.86-3.33-8.22-3.69-13.44-3.69-3.91,0-7.68,0.76-10.87,3-1.72,1.21-3.04,2.73-4.13,4.44l-3.03-0.56c1.08-1.63,2.34-2.91,3.75-3.91,2.69-1.89,7.78-3.37,15.5-3.38zm-1.22,13.22c1.61,0,2.63,0.2,3.5,0.38-2.14,0.54-5.01,1.15-8.03,1.75,0.47-0.85,0.89-1.39,1-1.47,0.26-0.18,1.3-0.66,3.53-0.66zm-13.47,17.66c-2.66,2.57-4.5,6.2-4.5,9.91,0,4.01,1.78,8.05,4.63,10.84,1.89,1.84,4.29,3.08,6.84,3.78-0.22,0.01-0.42,0.06-0.65,0.06-5.57,0-8.97-1.36-11.5-3.75-2.69-2.53-3.82-5.06-3.82-9.15,0-2.67,0.59-4.71,1.72-6.57,1.14-1.86,2.57-3.13,4.75-4.18,0.61-0.3,1.67-0.63,2.53-0.94zm18.5,6.44c-0.03,0.33-0.1,1.75-0.09,1.72v0.03l-0.03,0.03c-0.26,0.88-0.62,1.51-1.78,2.37-1.93,1.4-3.38,1.82-5.1,1.82-1.44,0-1.81-0.21-2.43-0.82-0.76-0.74-0.78-0.86-0.78-1.68,0-0.51-0.31-0.18,0.68-0.88-0.33,0.22,2.55-1,7.06-1.97,1.09-0.23,1.51-0.41,2.47-0.62z" fill="#000"/>
<path d="M191.4,122.86l-15.68-2.83c1.76-6.31,4.8-10.99,9.1-14.02,4.31-3.04,10.7-4.55,19.19-4.55,7.71,0,13.45,0.91,17.22,2.73,3.77,1.83,6.43,4.14,7.97,6.95,1.53,2.81,2.3,7.97,2.3,15.47l-0.18,20.17c0,5.74,0.28,9.98,0.83,12.7,0.55,2.73,1.59,5.65,3.1,8.77h-17.09c-0.45-1.15-1.01-2.85-1.66-5.11-0.29-1.02-0.5-1.7-0.62-2.03-2.95,2.87-6.11,5.03-9.47,6.46-3.36,1.44-6.95,2.15-10.76,2.15-6.73,0-12.03-1.82-15.9-5.47-3.88-3.65-5.81-8.26-5.81-13.84,0-3.69,0.88-6.98,2.64-9.87,1.77-2.89,4.24-5.1,7.41-6.64,3.18-1.54,7.76-2.88,13.75-4.03,8.08-1.52,13.67-2.93,16.79-4.24v-1.73c0-3.32-0.82-5.68-2.46-7.1-1.64-1.41-4.74-2.12-9.29-2.12-3.07,0-5.47,0.6-7.19,1.81-1.73,1.21-3.12,3.33-4.19,6.37zm23.13,14.02c-2.22,0.74-5.72,1.62-10.52,2.65-4.8,1.02-7.93,2.03-9.41,3.01-2.25,1.6-3.38,3.63-3.38,6.09,0,2.42,0.9,4.51,2.71,6.27,1.8,1.76,4.1,2.65,6.88,2.65,3.12,0,6.09-1.03,8.92-3.08,2.09-1.56,3.47-3.46,4.12-5.72,0.45-1.48,0.68-4.28,0.68-8.42v-3.45z" stroke="#500050" stroke-width="10" fill="none"/>
<path d="M191.4,122.86l-15.68-2.83c1.76-6.31,4.8-10.99,9.1-14.02,4.31-3.04,10.7-4.55,19.19-4.55,7.71,0,13.45,0.91,17.22,2.73,3.77,1.83,6.43,4.14,7.97,6.95,1.53,2.81,2.3,7.97,2.3,15.47l-0.18,20.17c0,5.74,0.28,9.98,0.83,12.7,0.55,2.73,1.59,5.65,3.1,8.77h-17.09c-0.45-1.15-1.01-2.85-1.66-5.11-0.29-1.02-0.5-1.7-0.62-2.03-2.95,2.87-6.11,5.03-9.47,6.46-3.36,1.44-6.95,2.15-10.76,2.15-6.73,0-12.03-1.82-15.9-5.47-3.88-3.65-5.81-8.26-5.81-13.84,0-3.69,0.88-6.98,2.64-9.87,1.77-2.89,4.24-5.1,7.41-6.64,3.18-1.54,7.76-2.88,13.75-4.03,8.08-1.52,13.67-2.93,16.79-4.24v-1.73c0-3.32-0.82-5.68-2.46-7.1-1.64-1.41-4.74-2.12-9.29-2.12-3.07,0-5.47,0.6-7.19,1.81-1.73,1.21-3.12,3.33-4.19,6.37zm23.13,14.02c-2.22,0.74-5.72,1.62-10.52,2.65-4.8,1.02-7.93,2.03-9.41,3.01-2.25,1.6-3.38,3.63-3.38,6.09,0,2.42,0.9,4.51,2.71,6.27,1.8,1.76,4.1,2.65,6.88,2.65,3.12,0,6.09-1.03,8.92-3.08,2.09-1.56,3.47-3.46,4.12-5.72,0.45-1.48,0.68-4.28,0.68-8.42v-3.45z" fill="url(#p)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,40 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1313609361(BasicNewsRecipe):
news = True
title = u'El Mostrador'
__author__ = 'Alex Mitrani'
description = u'Chilean online newspaper'
publisher = u'La Plaza S.A.'
category = 'news, rss'
oldest_article = 7
max_articles_per_feed = 100
summary_length = 1000
language = 'es_CL'
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_empty_feeds = True
masthead_url = 'http://www.elmostrador.cl/assets/img/logo-elmostrador-m.jpg'
remove_tags_before = dict(name='div', attrs={'class':'news-heading cf'})
remove_tags_after = dict(name='div', attrs={'class':'footer-actions cf'})
remove_tags = [dict(name='div', attrs={'class':'footer-actions cb cf'})
,dict(name='div', attrs={'class':'news-aside fl'})
,dict(name='div', attrs={'class':'footer-actions cf'})
,dict(name='div', attrs={'class':'user-bar','id':'top'})
,dict(name='div', attrs={'class':'indicators'})
,dict(name='div', attrs={'id':'header'})
]
feeds = [(u'Temas Destacados'
, u'http://www.elmostrador.cl/destacado/feed/')
, (u'El D\xeda', u'http://www.elmostrador.cl/dia/feed/')
, (u'Pa\xeds', u'http://www.elmostrador.cl/noticias/pais/feed/')
, (u'Mundo', u'http://www.elmostrador.cl/noticias/mundo/feed/')
, (u'Negocios', u'http://www.elmostrador.cl/noticias/negocios/feed/')
, (u'Cultura', u'http://www.elmostrador.cl/noticias/cultura/feed/')
, (u'Vida en L\xednea', u'http://www.elmostrador.cl/vida-en-linea/feed/')
, (u'Opini\xf3n & Blogs', u'http://www.elmostrador.cl/opinion/feed/')
]

View File

@ -2,6 +2,9 @@ from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1306097511(BasicNewsRecipe): class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Metro Nieuws NL' title = u'Metro Nieuws NL'
description = u'Metro Nieuws - NL'
# Version 1.2, updated cover image to match the changed website.
# added info date on title
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 100 max_articles_per_feed = 100
__author__ = u'DrMerry' __author__ = u'DrMerry'
@ -10,11 +13,11 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
simultaneous_downloads = 5 simultaneous_downloads = 5
delay = 1 delay = 1
# timefmt = ' [%A, %d %B, %Y]' # timefmt = ' [%A, %d %B, %Y]'
timefmt = '' timefmt = ' [%A, %d %b %Y]'
no_stylesheets = True no_stylesheets = True
remove_javascript = True remove_javascript = True
remove_empty_feeds = True remove_empty_feeds = True
cover_url = 'http://www.readmetro.com/img/en/metroholland/last/1/small.jpg' cover_url = 'http://www.oldreadmetro.com/img/en/metroholland/last/1/small.jpg'
remove_empty_feeds = True remove_empty_feeds = True
publication_type = 'newspaper' publication_type = 'newspaper'
remove_tags_before = dict(name='div', attrs={'id':'date'}) remove_tags_before = dict(name='div', attrs={'id':'date'})

View File

@ -0,0 +1,27 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1313555075(BasicNewsRecipe):
news = True
title = u'The Clinic'
__author__ = 'Alex Mitrani'
description = u'Online version of Chilean satirical weekly'
publisher = u'The Clinic'
category = 'news, politics, Chile, rss'
oldest_article = 7
max_articles_per_feed = 100
summary_length = 1000
language = 'es_CL'
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_empty_feeds = True
masthead_url = 'http://www.theclinic.cl/wp-content/themes/tc12m/css/ui/mainLogoTC-top.png'
remove_tags_before = dict(name='article', attrs={'class':'scope bordered'})
remove_tags_after = dict(name='div', attrs={'id':'commentsSection'})
remove_tags = [dict(name='span', attrs={'class':'relTags'})
,dict(name='div', attrs={'class':'articleActivity hdcol'})
,dict(name='div', attrs={'id':'commentsSection'})
]
feeds = [(u'The Clinic Online', u'http://www.theclinic.cl/feed/')]

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -291,6 +291,8 @@ class ISO639(Command):
by_3t = {} by_3t = {}
m2to3 = {} m2to3 = {}
m3to2 = {} m3to2 = {}
m3bto3t = {}
nm = {}
codes2, codes3t, codes3b = set([]), set([]), set([]) codes2, codes3t, codes3b = set([]), set([]), set([])
for x in root.xpath('//iso_639_entry'): for x in root.xpath('//iso_639_entry'):
name = x.get('name') name = x.get('name')
@ -304,12 +306,19 @@ class ISO639(Command):
m3to2[threeb] = m3to2[threet] = two m3to2[threeb] = m3to2[threet] = two
by_3b[threeb] = name by_3b[threeb] = name
by_3t[threet] = name by_3t[threet] = name
if threeb != threet:
m3bto3t[threeb] = threet
codes3b.add(x.get('iso_639_2B_code')) codes3b.add(x.get('iso_639_2B_code'))
codes3t.add(x.get('iso_639_2T_code')) codes3t.add(x.get('iso_639_2T_code'))
base_name = name.lower()
nm[base_name] = threet
simple_name = base_name.partition(';')[0].strip()
if simple_name not in nm:
nm[simple_name] = threet
from cPickle import dump from cPickle import dump
x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2, x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2,
'codes3b':codes3b, 'codes3t':codes3t, '2to3':m2to3, 'codes3b':codes3b, 'codes3t':codes3t, '2to3':m2to3,
'3to2':m3to2} '3to2':m3to2, '3bto3t':m3bto3t, 'name_map':nm}
dump(x, open(dest, 'wb'), -1) dump(x, open(dest, 'wb'), -1)

View File

@ -64,6 +64,7 @@ class ANDROID(USBMS):
0x6860 : [0x0400], 0x6860 : [0x0400],
0x6877 : [0x0400], 0x6877 : [0x0400],
0x689e : [0x0400], 0x689e : [0x0400],
0xdeed : [0x0222],
}, },
# Viewsonic # Viewsonic
@ -132,7 +133,7 @@ class ANDROID(USBMS):
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2', '7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK', 'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK',
'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612', 'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612',
'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870'] 'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',

View File

@ -64,7 +64,7 @@ class KINDLE(USBMS):
EBOOK_DIR_MAIN = 'documents' EBOOK_DIR_MAIN = 'documents'
EBOOK_DIR_CARD_A = 'documents' EBOOK_DIR_CARD_A = 'documents'
DELETE_EXTS = ['.mbp','.tan','.pdr'] DELETE_EXTS = ['.mbp', '.tan', '.pdr', '.ea', '.apnx', '.phl']
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
SUPPORTS_ANNOTATIONS = True SUPPORTS_ANNOTATIONS = True

View File

@ -252,8 +252,8 @@ class EEEREADER(USBMS):
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Book' EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Book'
VENDOR_NAME = 'LINUX' VENDOR_NAME = ['LINUX', 'ASUS']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'FILE-STOR_GADGET' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['FILE-STOR_GADGET', 'EEE_NOTE']
class ADAM(USBMS): class ADAM(USBMS):

View File

@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
import os, shutil, time import os, shutil, time
from calibre.devices.errors import PathError from calibre.devices.errors import PathError
from calibre.utils.filenames import case_preserving_open_file
class File(object): class File(object):
@ -46,10 +47,8 @@ class CLI(object):
path = os.path.join(path, infile.name) path = os.path.join(path, infile.name)
if not replace_file and os.path.exists(path): if not replace_file and os.path.exists(path):
raise PathError('File already exists: ' + path) raise PathError('File already exists: ' + path)
d = os.path.dirname(path) dest, actual_path = case_preserving_open_file(path)
if not os.path.exists(d): with dest:
os.makedirs(d)
with open(path, 'w+b') as dest:
try: try:
shutil.copyfileobj(infile, dest) shutil.copyfileobj(infile, dest)
except IOError: except IOError:
@ -62,6 +61,7 @@ class CLI(object):
#if not check_transfer(infile, dest): raise Exception('Transfer failed') #if not check_transfer(infile, dest): raise Exception('Transfer failed')
if close: if close:
infile.close() infile.close()
return actual_path
def munge_path(self, path): def munge_path(self, path):
if path.startswith('/') and not (path.startswith(self._main_prefix) or \ if path.startswith('/') and not (path.startswith(self._main_prefix) or \

View File

@ -258,10 +258,10 @@ class USBMS(CLI, Device):
for i, infile in enumerate(files): for i, infile in enumerate(files):
mdata, fname = metadata.next(), names.next() mdata, fname = metadata.next(), names.next()
filepath = self.normalize_path(self.create_upload_path(path, mdata, fname)) filepath = self.normalize_path(self.create_upload_path(path, mdata, fname))
paths.append(filepath)
if not hasattr(infile, 'read'): if not hasattr(infile, 'read'):
infile = self.normalize_path(infile) infile = self.normalize_path(infile)
self.put_file(infile, filepath, replace_file=True) filepath = self.put_file(infile, filepath, replace_file=True)
paths.append(filepath)
try: try:
self.upload_cover(os.path.dirname(filepath), self.upload_cover(os.path.dirname(filepath),
os.path.splitext(os.path.basename(filepath))[0], os.path.splitext(os.path.basename(filepath))[0],

View File

@ -28,8 +28,9 @@ class ParserError(ValueError):
BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'txtz', 'text', 'htm', 'xhtm', BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'txtz', 'text', 'htm', 'xhtm',
'html', 'htmlz', 'xhtml', 'pdf', 'pdb', 'pdr', 'prc', 'mobi', 'azw', 'doc', 'html', 'htmlz', 'xhtml', 'pdf', 'pdb', 'pdr', 'prc', 'mobi', 'azw', 'doc',
'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip', 'epub', 'fb2', 'djv', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip',
'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'pmlz', 'mbp', 'tan', 'snb'] 'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'pmlz', 'mbp', 'tan', 'snb',
'xps', 'oxps']
class HTMLRenderer(object): class HTMLRenderer(object):

View File

@ -47,8 +47,7 @@ PUBLICATION_METADATA_FIELDS = frozenset([
# If None, means book # If None, means book
'publication_type', 'publication_type',
'uuid', # A UUID usually of type 4 'uuid', # A UUID usually of type 4
'language', # the primary language of this book 'languages', # ordered list of languages in this publication
'languages', # ordered list
'publisher', # Simple string, no special semantics 'publisher', # Simple string, no special semantics
# Absolute path to image file encoded in filesystem_encoding # Absolute path to image file encoded in filesystem_encoding
'cover', 'cover',
@ -109,7 +108,7 @@ STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
# Metadata fields that smart update must do special processing to copy. # Metadata fields that smart update must do special processing to copy.
SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors', SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors',
'author_sort', 'author_sort_map', 'author_sort', 'author_sort_map',
'cover_data', 'tags', 'language', 'cover_data', 'tags', 'languages',
'identifiers']) 'identifiers'])
# Metadata fields that smart update should copy only if the source is not None # Metadata fields that smart update should copy only if the source is not None

View File

@ -102,6 +102,7 @@ class Metadata(object):
@param other: None or a metadata object @param other: None or a metadata object
''' '''
_data = copy.deepcopy(NULL_VALUES) _data = copy.deepcopy(NULL_VALUES)
_data.pop('language')
object.__setattr__(self, '_data', _data) object.__setattr__(self, '_data', _data)
if other is not None: if other is not None:
self.smart_update(other) self.smart_update(other)
@ -136,6 +137,11 @@ class Metadata(object):
_data = object.__getattribute__(self, '_data') _data = object.__getattribute__(self, '_data')
if field in TOP_LEVEL_IDENTIFIERS: if field in TOP_LEVEL_IDENTIFIERS:
return _data.get('identifiers').get(field, None) return _data.get('identifiers').get(field, None)
if field == 'language':
try:
return _data.get('languages', [])[0]
except:
return NULL_VALUES['language']
if field in STANDARD_METADATA_FIELDS: if field in STANDARD_METADATA_FIELDS:
return _data.get(field, None) return _data.get(field, None)
try: try:
@ -175,6 +181,11 @@ class Metadata(object):
if not val: if not val:
val = copy.copy(NULL_VALUES.get('identifiers', None)) val = copy.copy(NULL_VALUES.get('identifiers', None))
self.set_identifiers(val) self.set_identifiers(val)
elif field == 'language':
langs = []
if val and val.lower() != 'und':
langs = [val]
_data['languages'] = langs
elif field in STANDARD_METADATA_FIELDS: elif field in STANDARD_METADATA_FIELDS:
if val is None: if val is None:
val = copy.copy(NULL_VALUES.get(field, None)) val = copy.copy(NULL_VALUES.get(field, None))
@ -553,9 +564,9 @@ class Metadata(object):
for attr in TOP_LEVEL_IDENTIFIERS: for attr in TOP_LEVEL_IDENTIFIERS:
copy_not_none(self, other, attr) copy_not_none(self, other, attr)
other_lang = getattr(other, 'language', None) other_lang = getattr(other, 'languages', [])
if other_lang and other_lang.lower() != 'und': if other_lang and other_lang != ['und']:
self.language = other_lang self.languages = list(other_lang)
if not getattr(self, 'series', None): if not getattr(self, 'series', None):
self.series_index = None self.series_index = None
@ -706,8 +717,8 @@ class Metadata(object):
fmt('Tags', u', '.join([unicode(t) for t in self.tags])) fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
if self.series: if self.series:
fmt('Series', self.series + ' #%s'%self.format_series_index()) fmt('Series', self.series + ' #%s'%self.format_series_index())
if not self.is_null('language'): if not self.is_null('languages'):
fmt('Language', self.language) fmt('Languages', ', '.join(self.languages))
if self.rating is not None: if self.rating is not None:
fmt('Rating', self.rating) fmt('Rating', self.rating)
if self.timestamp is not None: if self.timestamp is not None:
@ -743,7 +754,7 @@ class Metadata(object):
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))] ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
if self.series: if self.series:
ans += [(_('Series'), unicode(self.series) + ' #%s'%self.format_series_index())] ans += [(_('Series'), unicode(self.series) + ' #%s'%self.format_series_index())]
ans += [(_('Language'), unicode(self.language))] ans += [(_('Languages'), u', '.join(self.languages))]
if self.timestamp is not None: if self.timestamp is not None:
ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))] ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
if self.pubdate is not None: if self.pubdate is not None:

View File

@ -19,7 +19,7 @@ from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import parse_date, isoformat from calibre.utils.date import parse_date, isoformat
from calibre.utils.localization import get_lang from calibre.utils.localization import get_lang, canonicalize_lang
from calibre import prints, guess_type from calibre import prints, guess_type
from calibre.utils.cleantext import clean_ascii_chars from calibre.utils.cleantext import clean_ascii_chars
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
@ -515,6 +515,7 @@ class OPF(object): # {{{
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]') '(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
uuid_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+ uuid_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
'(re:match(@opf:scheme, "uuid", "i") or re:match(@scheme, "uuid", "i"))]') '(re:match(@opf:scheme, "uuid", "i") or re:match(@scheme, "uuid", "i"))]')
languages_path = XPath('descendant::*[local-name()="language"]')
manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]') manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]') manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
@ -523,7 +524,6 @@ class OPF(object): # {{{
title = MetadataField('title', formatter=lambda x: re.sub(r'\s+', ' ', x)) title = MetadataField('title', formatter=lambda x: re.sub(r'\s+', ' ', x))
publisher = MetadataField('publisher') publisher = MetadataField('publisher')
language = MetadataField('language')
comments = MetadataField('description') comments = MetadataField('description')
category = MetadataField('type') category = MetadataField('type')
rights = MetadataField('rights') rights = MetadataField('rights')
@ -930,6 +930,44 @@ class OPF(object): # {{{
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@dynamic_property
def language(self):
def fget(self):
ans = self.languages
if ans:
return ans[0]
def fset(self, val):
self.languages = [val]
return property(fget=fget, fset=fset)
@dynamic_property
def languages(self):
def fget(self):
ans = []
for match in self.languages_path(self.metadata):
t = self.get_text(match)
if t and t.strip():
l = canonicalize_lang(t.strip())
if l:
ans.append(l)
return ans
def fset(self, val):
matches = self.languages_path(self.metadata)
for x in matches:
x.getparent().remove(x)
for lang in val:
l = self.create_metadata_element('language')
self.set_text(l, unicode(lang))
return property(fget=fget, fset=fset)
@dynamic_property @dynamic_property
def book_producer(self): def book_producer(self):
@ -1052,9 +1090,9 @@ class OPF(object): # {{{
val = getattr(mi, attr, None) val = getattr(mi, attr, None)
if val is not None and val != [] and val != (None, None): if val is not None and val != [] and val != (None, None):
setattr(self, attr, val) setattr(self, attr, val)
lang = getattr(mi, 'language', None) langs = getattr(mi, 'languages', [])
if lang and lang != 'und': if langs and langs != ['und']:
self.language = lang self.languages = langs
temp = self.to_book_metadata() temp = self.to_book_metadata()
temp.smart_update(mi, replace_metadata=replace_metadata) temp.smart_update(mi, replace_metadata=replace_metadata)
self._user_metadata_ = temp.get_all_user_metadata(True) self._user_metadata_ = temp.get_all_user_metadata(True)
@ -1202,10 +1240,11 @@ class OPFCreator(Metadata):
dc_attrs={'id':__appname__+'_id'})) dc_attrs={'id':__appname__+'_id'}))
if getattr(self, 'pubdate', None) is not None: if getattr(self, 'pubdate', None) is not None:
a(DC_ELEM('date', self.pubdate.isoformat())) a(DC_ELEM('date', self.pubdate.isoformat()))
lang = self.language langs = self.languages
if not lang or lang.lower() == 'und': if not langs or langs == ['und']:
lang = get_lang().replace('_', '-') langs = [get_lang().replace('_', '-').partition('-')[0]]
a(DC_ELEM('language', lang)) for lang in langs:
a(DC_ELEM('language', lang))
if self.comments: if self.comments:
a(DC_ELEM('description', self.comments)) a(DC_ELEM('description', self.comments))
if self.publisher: if self.publisher:
@ -1288,8 +1327,9 @@ def metadata_to_opf(mi, as_string=True):
mi.book_producer = __appname__ + ' (%s) '%__version__ + \ mi.book_producer = __appname__ + ' (%s) '%__version__ + \
'[http://calibre-ebook.com]' '[http://calibre-ebook.com]'
if not mi.language: if not mi.languages:
mi.language = 'UND' lang = get_lang().replace('_', '-').partition('-')[0]
mi.languages = [lang]
root = etree.fromstring(textwrap.dedent( root = etree.fromstring(textwrap.dedent(
''' '''
@ -1339,8 +1379,10 @@ def metadata_to_opf(mi, as_string=True):
factory(DC('identifier'), val, scheme=icu_upper(key)) factory(DC('identifier'), val, scheme=icu_upper(key))
if mi.rights: if mi.rights:
factory(DC('rights'), mi.rights) factory(DC('rights'), mi.rights)
factory(DC('language'), mi.language if mi.language and mi.language.lower() for lang in mi.languages:
!= 'und' else get_lang().replace('_', '-')) if not lang or lang.lower() == 'und':
continue
factory(DC('language'), lang)
if mi.tags: if mi.tags:
for tag in mi.tags: for tag in mi.tags:
factory(DC('subject'), tag) factory(DC('subject'), tag)

View File

@ -22,6 +22,7 @@ from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.library.comments import sanitize_comments_html from calibre.library.comments import sanitize_comments_html
from calibre.utils.date import parse_date from calibre.utils.date import parse_date
from calibre.utils.localization import canonicalize_lang
class Worker(Thread): # Get details {{{ class Worker(Thread): # Get details {{{
@ -106,10 +107,11 @@ class Worker(Thread): # Get details {{{
r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}') r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}')
lm = { lm = {
'en': ('English', 'Englisch'), 'eng': ('English', 'Englisch'),
'fr': ('French', 'Français'), 'fra': ('French', 'Français'),
'it': ('Italian', 'Italiano'), 'ita': ('Italian', 'Italiano'),
'de': ('German', 'Deutsch'), 'deu': ('German', 'Deutsch'),
'spa': ('Spanish', 'Espa\xf1ol', 'Espaniol'),
} }
self.lang_map = {} self.lang_map = {}
for code, names in lm.iteritems(): for code, names in lm.iteritems():
@ -374,8 +376,11 @@ class Worker(Thread): # Get details {{{
def parse_language(self, pd): def parse_language(self, pd):
for x in reversed(pd.xpath(self.language_xpath)): for x in reversed(pd.xpath(self.language_xpath)):
if x.tail: if x.tail:
ans = x.tail.strip() raw = x.tail.strip()
ans = self.lang_map.get(ans, None) ans = self.lang_map.get(raw, None)
if ans:
return ans
ans = canonicalize_lang(ans)
if ans: if ans:
return ans return ans
# }}} # }}}
@ -388,7 +393,7 @@ class Amazon(Source):
capabilities = frozenset(['identify', 'cover']) capabilities = frozenset(['identify', 'cover'])
touched_fields = frozenset(['title', 'authors', 'identifier:amazon', touched_fields = frozenset(['title', 'authors', 'identifier:amazon',
'identifier:isbn', 'rating', 'comments', 'publisher', 'pubdate', 'identifier:isbn', 'rating', 'comments', 'publisher', 'pubdate',
'language']) 'languages'])
has_html_comments = True has_html_comments = True
supports_gzip_transfer_encoding = True supports_gzip_transfer_encoding = True

View File

@ -20,6 +20,7 @@ from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.chardet import xml_to_unicode
from calibre.utils.date import parse_date, utcnow from calibre.utils.date import parse_date, utcnow
from calibre.utils.cleantext import clean_ascii_chars from calibre.utils.cleantext import clean_ascii_chars
from calibre.utils.localization import canonicalize_lang
from calibre import as_unicode from calibre import as_unicode
NAMESPACES = { NAMESPACES = {
@ -95,7 +96,9 @@ def to_metadata(browser, log, entry_, timeout): # {{{
return mi return mi
mi.comments = get_text(extra, description) mi.comments = get_text(extra, description)
#mi.language = get_text(extra, language) lang = canonicalize_lang(get_text(extra, language))
if lang:
mi.language = lang
mi.publisher = get_text(extra, publisher) mi.publisher = get_text(extra, publisher)
# ISBN # ISBN
@ -162,7 +165,7 @@ class GoogleBooks(Source):
capabilities = frozenset(['identify', 'cover']) capabilities = frozenset(['identify', 'cover'])
touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate', touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate',
'comments', 'publisher', 'identifier:isbn', 'rating', 'comments', 'publisher', 'identifier:isbn', 'rating',
'identifier:google']) # language currently disabled 'identifier:google', 'languages'])
supports_gzip_transfer_encoding = True supports_gzip_transfer_encoding = True
cached_cover_url_is_reliable = False cached_cover_url_is_reliable = False

View File

@ -484,6 +484,7 @@ def identify(log, abort, # {{{
'publication dates') 'publication dates')
start_time = time.time() start_time = time.time()
results = merge_identify_results(results, log) results = merge_identify_results(results, log)
log('We have %d merged results, merging took: %.2f seconds' % log('We have %d merged results, merging took: %.2f seconds' %
(len(results), time.time() - start_time)) (len(results), time.time() - start_time))

View File

@ -35,7 +35,7 @@ class OverDrive(Source):
capabilities = frozenset(['identify', 'cover']) capabilities = frozenset(['identify', 'cover'])
touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate', touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate',
'comments', 'publisher', 'identifier:isbn', 'series', 'series_index', 'comments', 'publisher', 'identifier:isbn', 'series', 'series_index',
'language', 'identifier:overdrive']) 'languages', 'identifier:overdrive'])
has_html_comments = True has_html_comments = True
supports_gzip_transfer_encoding = False supports_gzip_transfer_encoding = False
cached_cover_url_is_reliable = True cached_cover_url_is_reliable = True
@ -421,8 +421,10 @@ class OverDrive(Source):
pass pass
if lang: if lang:
lang = lang[0].strip().lower() lang = lang[0].strip().lower()
mi.language = {'english':'en', 'french':'fr', 'german':'de', lang = {'english':'eng', 'french':'fra', 'german':'deu',
'spanish':'es'}.get(lang, None) 'spanish':'spa'}.get(lang, None)
if lang:
mi.language = lang
if ebook_isbn: if ebook_isbn:
#print "ebook isbn is "+str(ebook_isbn[0]) #print "ebook isbn is "+str(ebook_isbn[0])

View File

@ -4,6 +4,7 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from struct import pack from struct import pack
from calibre.utils.localization import lang_as_iso639_1
lang_codes = { lang_codes = {
} }
@ -314,7 +315,8 @@ def iana2mobi(icode):
subtags = list(icode.split('-')) subtags = list(icode.split('-'))
while len(subtags) > 0: while len(subtags) > 0:
lang = subtags.pop(0).lower() lang = subtags.pop(0).lower()
if lang in IANA_MOBI: lang = lang_as_iso639_1(lang)
if lang and lang in IANA_MOBI:
langdict = IANA_MOBI[lang] langdict = IANA_MOBI[lang]
break break

View File

@ -61,9 +61,11 @@ def meta_info_to_oeb_metadata(mi, m, log, override_input_metadata=False):
m.add('identifier', val, scheme=typ.upper()) m.add('identifier', val, scheme=typ.upper())
if override_input_metadata and not set_isbn: if override_input_metadata and not set_isbn:
m.filter('identifier', lambda x: x.scheme.lower() == 'isbn') m.filter('identifier', lambda x: x.scheme.lower() == 'isbn')
if not mi.is_null('language'): if not mi.is_null('languages'):
m.clear('language') m.clear('language')
m.add('language', mi.language) for lang in mi.languages:
if lang and lang.lower() not in ('und', ''):
m.add('language', lang)
if not mi.is_null('series_index'): if not mi.is_null('series_index'):
m.clear('series_index') m.clear('series_index')
m.add('series_index', mi.format_series_index()) m.add('series_index', mi.format_series_index())

View File

@ -94,7 +94,7 @@ gprefs.defaults['book_display_fields'] = [
('path', True), ('publisher', False), ('rating', False), ('path', True), ('publisher', False), ('rating', False),
('author_sort', False), ('sort', False), ('timestamp', False), ('author_sort', False), ('sort', False), ('timestamp', False),
('uuid', False), ('comments', True), ('id', False), ('pubdate', False), ('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
('last_modified', False), ('size', False), ('last_modified', False), ('size', False), ('languages', False),
] ]
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}' gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True gprefs.defaults['preserve_date_on_ctl'] = True

View File

@ -24,6 +24,7 @@ from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data,
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.formatter import EvalFormatter from calibre.utils.formatter import EvalFormatter
from calibre.utils.date import is_date_undefined from calibre.utils.date import is_date_undefined
from calibre.utils.localization import calibre_langcode_to_name
def render_html(mi, css, vertical, widget, all_fields=False): # {{{ def render_html(mi, css, vertical, widget, all_fields=False): # {{{
table = render_data(mi, all_fields=all_fields, table = render_data(mi, all_fields=all_fields,
@ -152,6 +153,12 @@ def render_data(mi, use_roman_numbers=True, all_fields=False):
authors.append(aut) authors.append(aut)
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
u' & '.join(authors)))) u' & '.join(authors))))
elif field == 'languages':
if not mi.languages:
continue
names = filter(None, map(calibre_langcode_to_name, mi.languages))
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
u', '.join(names))))
else: else:
val = mi.format_field(field)[-1] val = mi.format_field(field)[-1]
if val is None: if val is None:

View File

@ -134,7 +134,7 @@ class MyBlockingBusy(QDialog): # {{{
do_autonumber, do_remove_format, remove_format, do_swap_ta, \ do_autonumber, do_remove_format, remove_format, do_swap_ta, \
do_remove_conv, do_auto_author, series, do_series_restart, \ do_remove_conv, do_auto_author, series, do_series_restart, \
series_start_value, do_title_case, cover_action, clear_series, \ series_start_value, do_title_case, cover_action, clear_series, \
pubdate, adddate, do_title_sort = self.args pubdate, adddate, do_title_sort, languages, clear_languages = self.args
# first loop: do author and title. These will commit at the end of each # first loop: do author and title. These will commit at the end of each
@ -238,6 +238,12 @@ class MyBlockingBusy(QDialog): # {{{
if do_remove_conv: if do_remove_conv:
self.db.delete_conversion_options(id, 'PIPE', commit=False) self.db.delete_conversion_options(id, 'PIPE', commit=False)
if clear_languages:
self.db.set_languages(id, [], notify=False, commit=False)
elif languages:
self.db.set_languages(id, languages, notify=False, commit=False)
elif self.current_phase == 3: elif self.current_phase == 3:
# both of these are fast enough to just do them all # both of these are fast enough to just do them all
for w in self.cc_widgets: for w in self.cc_widgets:
@ -329,6 +335,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
geom = gprefs.get('bulk_metadata_window_geometry', None) geom = gprefs.get('bulk_metadata_window_geometry', None)
if geom is not None: if geom is not None:
self.restoreGeometry(bytes(geom)) self.restoreGeometry(bytes(geom))
self.languages.setEditText('')
self.exec_() self.exec_()
def save_state(self, *args): def save_state(self, *args):
@ -352,6 +359,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.do_again = True self.do_again = True
self.accept() self.accept()
# S&R {{{
def prepare_search_and_replace(self): def prepare_search_and_replace(self):
self.search_for.initialize('bulk_edit_search_for') self.search_for.initialize('bulk_edit_search_for')
self.replace_with.initialize('bulk_edit_replace_with') self.replace_with.initialize('bulk_edit_replace_with')
@ -796,6 +804,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
# permanent. Make sure it really is. # permanent. Make sure it really is.
self.db.commit() self.db.commit()
self.model.refresh_ids(list(books_to_refresh)) self.model.refresh_ids(list(books_to_refresh))
# }}}
def create_custom_column_editors(self): def create_custom_column_editors(self):
w = self.central_widget.widget(1) w = self.central_widget.widget(1)
@ -919,6 +928,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
do_auto_author = self.auto_author_sort.isChecked() do_auto_author = self.auto_author_sort.isChecked()
do_title_case = self.change_title_to_title_case.isChecked() do_title_case = self.change_title_to_title_case.isChecked()
do_title_sort = self.update_title_sort.isChecked() do_title_sort = self.update_title_sort.isChecked()
clear_languages = self.clear_languages.isChecked()
languages = self.languages.lang_codes
pubdate = adddate = None pubdate = adddate = None
if self.apply_pubdate.isChecked(): if self.apply_pubdate.isChecked():
pubdate = qt_to_dt(self.pubdate.date()) pubdate = qt_to_dt(self.pubdate.date())
@ -937,7 +948,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
do_autonumber, do_remove_format, remove_format, do_swap_ta, do_autonumber, do_remove_format, remove_format, do_swap_ta,
do_remove_conv, do_auto_author, series, do_series_restart, do_remove_conv, do_auto_author, series, do_series_restart,
series_start_value, do_title_case, cover_action, clear_series, series_start_value, do_title_case, cover_action, clear_series,
pubdate, adddate, do_title_sort) pubdate, adddate, do_title_sort, languages, clear_languages)
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.') bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
%len(self.ids), args, self.db, self.ids, %len(self.ids), args, self.db, self.ids,

View File

@ -443,7 +443,7 @@ from the value in the box</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="11" column="0"> <item row="13" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>Remove &amp;format:</string> <string>Remove &amp;format:</string>
@ -453,7 +453,7 @@ from the value in the box</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="11" column="1"> <item row="13" column="1">
<widget class="QComboBox" name="remove_format"> <widget class="QComboBox" name="remove_format">
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
@ -463,7 +463,7 @@ from the value in the box</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="0"> <item row="14" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -479,7 +479,7 @@ from the value in the box</string>
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="13" column="0" colspan="3"> <item row="15" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<item> <item>
<widget class="QCheckBox" name="change_title_to_title_case"> <widget class="QCheckBox" name="change_title_to_title_case">
@ -529,7 +529,7 @@ Future conversion of these books will use the default settings.</string>
</item> </item>
</layout> </layout>
</item> </item>
<item row="14" column="0" colspan="3"> <item row="16" column="0" colspan="3">
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
<string>Change &amp;cover</string> <string>Change &amp;cover</string>
@ -559,7 +559,7 @@ Future conversion of these books will use the default settings.</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="15" column="0"> <item row="17" column="0">
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -572,6 +572,29 @@ Future conversion of these books will use the default settings.</string>
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="11" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&amp;Languages:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>languages</cstring>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="LanguagesEdit" name="languages"/>
</item>
<item row="11" column="2">
<widget class="QCheckBox" name="clear_languages">
<property name="text">
<string>Remove &amp;all</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="tab">
@ -1145,6 +1168,11 @@ not multiple and the destination field is multiple</string>
<extends>QLineEdit</extends> <extends>QLineEdit</extends>
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>
<customwidget>
<class>LanguagesEdit</class>
<extends>QComboBox</extends>
<header>calibre/gui2/languages.h</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>authors</tabstop> <tabstop>authors</tabstop>

View File

@ -20,7 +20,7 @@ from calibre.constants import DEBUG
from calibre import prints from calibre import prints
from calibre.utils.icu import sort_key, lower from calibre.utils.icu import sort_key, lower
from calibre.gui2 import NONE, error_dialog, info_dialog from calibre.gui2 import NONE, error_dialog, info_dialog
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser, ParseException
from calibre.gui2.search_box import SearchBox2 from calibre.gui2.search_box import SearchBox2
ROOT = QModelIndex() ROOT = QModelIndex()
@ -53,6 +53,7 @@ def finalize(shortcuts, custom_keys_map={}): # {{{
if DEBUG: if DEBUG:
prints('Key %r for shortcut %s is already used by' prints('Key %r for shortcut %s is already used by'
' %s, ignoring'%(x, shortcut['name'], seen[x]['name'])) ' %s, ignoring'%(x, shortcut['name'], seen[x]['name']))
keys_map[unique_name] = ()
continue continue
seen[x] = shortcut seen[x] = shortcut
keys.append(ks) keys.append(ks)
@ -113,6 +114,8 @@ class Manager(QObject): # {{{
custom_keys_map = {un:tuple(keys) for un, keys in self.config.get( custom_keys_map = {un:tuple(keys) for un, keys in self.config.get(
'map', {}).iteritems()} 'map', {}).iteritems()}
self.keys_map = finalize(self.shortcuts, custom_keys_map=custom_keys_map) self.keys_map = finalize(self.shortcuts, custom_keys_map=custom_keys_map)
#import pprint
#pprint.pprint(self.keys_map)
# }}} # }}}
@ -149,7 +152,7 @@ class ConfigModel(QAbstractItemModel, SearchQueryParser):
shortcut_map = {k:v.copy() for k, v in shortcut_map = {k:v.copy() for k, v in
self.keyboard.shortcuts.iteritems()} self.keyboard.shortcuts.iteritems()}
for un, s in shortcut_map.iteritems(): for un, s in shortcut_map.iteritems():
s['keys'] = tuple(self.keyboard.keys_map[un]) s['keys'] = tuple(self.keyboard.keys_map.get(un, ()))
s['unique_name'] = un s['unique_name'] = un
s['group'] = [g for g, names in self.keyboard.groups.iteritems() if un in s['group'] = [g for g, names in self.keyboard.groups.iteritems() if un in
names][0] names][0]
@ -590,11 +593,19 @@ class ShortcutConfig(QWidget): # {{{
return self.view.state() == self.view.EditingState return self.view.state() == self.view.EditingState
def find(self, query): def find(self, query):
idx = self._model.find(query) if not query:
return
try:
idx = self._model.find(query)
except ParseException:
self.search.search_done(False)
return
self.search.search_done(True)
if not idx.isValid(): if not idx.isValid():
return info_dialog(self, _('No matches'), info_dialog(self, _('No matches'),
_('Could not find any matching shortcuts'), show=True, _('Could not find any shortcuts matching %s')%query,
show_copy_button=False) show=True, show_copy_button=False)
return
self.highlight_index(idx) self.highlight_index(idx)
def highlight_index(self, idx): def highlight_index(self, idx):
@ -602,6 +613,7 @@ class ShortcutConfig(QWidget): # {{{
self.view.selectionModel().select(idx, self.view.selectionModel().select(idx,
self.view.selectionModel().ClearAndSelect) self.view.selectionModel().ClearAndSelect)
self.view.setCurrentIndex(idx) self.view.setCurrentIndex(idx)
self.view.setFocus(Qt.OtherFocusReason)
def find_next(self, *args): def find_next(self, *args):
idx = self.view.currentIndex() idx = self.view.currentIndex()

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from calibre.gui2.complete import MultiCompleteComboBox
from calibre.utils.localization import lang_map
from calibre.utils.icu import sort_key
class LanguagesEdit(MultiCompleteComboBox):
def __init__(self, parent=None):
MultiCompleteComboBox.__init__(self, parent)
self._lang_map = lang_map()
self._rmap = {v:k for k,v in self._lang_map.iteritems()}
all_items = sorted(self._lang_map.itervalues(),
key=sort_key)
self.update_items_cache(all_items)
for item in all_items:
self.addItem(item)
@dynamic_property
def lang_codes(self):
def fget(self):
vals = [x.strip() for x in
unicode(self.lineEdit().text()).split(',')]
ans = []
for name in vals:
if name:
code = self._rmap.get(name, None)
if code is not None:
ans.append(code)
return ans
def fset(self, lang_codes):
ans = []
for lc in lang_codes:
name = self._lang_map.get(lc, None)
if name is not None:
ans.append(name)
self.setEditText(', '.join(ans))
return property(fget=fget, fset=fset)
def validate(self):
vals = [x.strip() for x in
unicode(self.lineEdit().text()).split(',')]
bad = []
for name in vals:
if name:
code = self._rmap.get(name, None)
if code is None:
bad.append(name)
return bad

View File

@ -23,6 +23,7 @@ from calibre.utils.formatter import validation_formatter
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.gui2.dialogs.comments_dialog import CommentsDialog from calibre.gui2.dialogs.comments_dialog import CommentsDialog
from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.gui2.languages import LanguagesEdit
class RatingDelegate(QStyledItemDelegate): # {{{ class RatingDelegate(QStyledItemDelegate): # {{{
@ -155,7 +156,7 @@ class TextDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent): def __init__(self, parent):
''' '''
Delegate for text data. If auto_complete_function needs to return a list Delegate for text data. If auto_complete_function needs to return a list
of text items to auto-complete with. The funciton is None no of text items to auto-complete with. If the function is None no
auto-complete will be used. auto-complete will be used.
''' '''
QStyledItemDelegate.__init__(self, parent) QStyledItemDelegate.__init__(self, parent)
@ -229,6 +230,20 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
QStyledItemDelegate.setModelData(self, editor, model, index) QStyledItemDelegate.setModelData(self, editor, model, index)
# }}} # }}}
class LanguagesDelegate(QStyledItemDelegate): # {{{
def createEditor(self, parent, option, index):
editor = LanguagesEdit(parent)
ct = index.data(Qt.DisplayRole).toString()
editor.setEditText(ct)
editor.lineEdit().selectAll()
return editor
def setModelData(self, editor, model, index):
val = ','.join(editor.lang_codes)
model.setData(index, QVariant(val), Qt.EditRole)
# }}}
class CcDateDelegate(QStyledItemDelegate): # {{{ class CcDateDelegate(QStyledItemDelegate): # {{{
''' '''
Delegate for custom columns dates. Because this delegate stores the Delegate for custom columns dates. Because this delegate stores the

View File

@ -25,6 +25,7 @@ from calibre.library.caches import (_match, CONTAINS_MATCH, EQUALS_MATCH,
from calibre import strftime, isbytestring from calibre import strftime, isbytestring
from calibre.constants import filesystem_encoding, DEBUG from calibre.constants import filesystem_encoding, DEBUG
from calibre.gui2.library import DEFAULT_SORT from calibre.gui2.library import DEFAULT_SORT
from calibre.utils.localization import calibre_langcode_to_name
def human_readable(size, precision=1): def human_readable(size, precision=1):
""" Convert a size in bytes into megabytes """ """ Convert a size in bytes into megabytes """
@ -64,6 +65,7 @@ class BooksModel(QAbstractTableModel): # {{{
'tags' : _("Tags"), 'tags' : _("Tags"),
'series' : ngettext("Series", 'Series', 1), 'series' : ngettext("Series", 'Series', 1),
'last_modified' : _('Modified'), 'last_modified' : _('Modified'),
'languages' : _('Languages'),
} }
def __init__(self, parent=None, buffer=40): def __init__(self, parent=None, buffer=40):
@ -71,7 +73,8 @@ class BooksModel(QAbstractTableModel): # {{{
self.db = None self.db = None
self.book_on_device = None self.book_on_device = None
self.editable_cols = ['title', 'authors', 'rating', 'publisher', self.editable_cols = ['title', 'authors', 'rating', 'publisher',
'tags', 'series', 'timestamp', 'pubdate'] 'tags', 'series', 'timestamp', 'pubdate',
'languages']
self.default_image = default_image() self.default_image = default_image()
self.sorted_on = DEFAULT_SORT self.sorted_on = DEFAULT_SORT
self.sort_history = [self.sorted_on] self.sort_history = [self.sorted_on]
@ -540,6 +543,13 @@ class BooksModel(QAbstractTableModel): # {{{
else: else:
return None return None
def languages(r, idx=-1):
lc = self.db.data[r][idx]
if lc:
langs = [calibre_langcode_to_name(l.strip()) for l in lc.split(',')]
return QVariant(', '.join(langs))
return None
def tags(r, idx=-1): def tags(r, idx=-1):
tags = self.db.data[r][idx] tags = self.db.data[r][idx]
if tags: if tags:
@ -641,6 +651,8 @@ class BooksModel(QAbstractTableModel): # {{{
siix=self.db.field_metadata['series_index']['rec_index']), siix=self.db.field_metadata['series_index']['rec_index']),
'ondevice' : functools.partial(text_type, 'ondevice' : functools.partial(text_type,
idx=self.db.field_metadata['ondevice']['rec_index'], mult=None), idx=self.db.field_metadata['ondevice']['rec_index'], mult=None),
'languages': functools.partial(languages,
idx=self.db.field_metadata['languages']['rec_index']),
} }
self.dc_decorator = { self.dc_decorator = {
@ -884,6 +896,9 @@ class BooksModel(QAbstractTableModel): # {{{
if val.isNull() or not val.isValid(): if val.isNull() or not val.isValid():
return False return False
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False)) self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
elif column == 'languages':
val = val.split(',')
self.db.set_languages(id, val)
else: else:
books_to_refresh |= self.db.set(row, column, val, books_to_refresh |= self.db.set(row, column, val,
allow_case_change=True) allow_case_change=True)

View File

@ -8,14 +8,14 @@ __docformat__ = 'restructuredtext en'
import os import os
from functools import partial from functools import partial
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \ from PyQt4.Qt import (QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal,
QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication, \ QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication,
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect)
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \ from calibre.gui2.library.delegates import (RatingDelegate, PubDateDelegate,
TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate, \ TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate,
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate, \ CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate,
CcEnumDelegate, CcNumberDelegate CcEnumDelegate, CcNumberDelegate, LanguagesDelegate)
from calibre.gui2.library.models import BooksModel, DeviceBooksModel from calibre.gui2.library.models import BooksModel, DeviceBooksModel
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks, prefs
from calibre.gui2 import error_dialog, gprefs from calibre.gui2 import error_dialog, gprefs
@ -85,6 +85,7 @@ class BooksView(QTableView): # {{{
self.pubdate_delegate = PubDateDelegate(self) self.pubdate_delegate = PubDateDelegate(self)
self.last_modified_delegate = DateDelegate(self, self.last_modified_delegate = DateDelegate(self,
tweak_name='gui_last_modified_display_format') tweak_name='gui_last_modified_display_format')
self.languages_delegate = LanguagesDelegate(self)
self.tags_delegate = CompleteDelegate(self, ',', 'all_tags') self.tags_delegate = CompleteDelegate(self, ',', 'all_tags')
self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True) self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True) self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
@ -306,6 +307,7 @@ class BooksView(QTableView): # {{{
state['hidden_columns'] = [cm[i] for i in range(h.count()) state['hidden_columns'] = [cm[i] for i in range(h.count())
if h.isSectionHidden(i) and cm[i] != 'ondevice'] if h.isSectionHidden(i) and cm[i] != 'ondevice']
state['last_modified_injected'] = True state['last_modified_injected'] = True
state['languages_injected'] = True
state['sort_history'] = \ state['sort_history'] = \
self.cleanup_sort_history(self.model().sort_history) self.cleanup_sort_history(self.model().sort_history)
state['column_positions'] = {} state['column_positions'] = {}
@ -390,7 +392,7 @@ class BooksView(QTableView): # {{{
def get_default_state(self): def get_default_state(self):
old_state = { old_state = {
'hidden_columns': ['last_modified'], 'hidden_columns': ['last_modified', 'languages'],
'sort_history':[DEFAULT_SORT], 'sort_history':[DEFAULT_SORT],
'column_positions': {}, 'column_positions': {},
'column_sizes': {}, 'column_sizes': {},
@ -399,6 +401,7 @@ class BooksView(QTableView): # {{{
'timestamp':'center', 'timestamp':'center',
'pubdate':'center'}, 'pubdate':'center'},
'last_modified_injected': True, 'last_modified_injected': True,
'languages_injected': True,
} }
h = self.column_header h = self.column_header
cm = self.column_map cm = self.column_map
@ -430,11 +433,20 @@ class BooksView(QTableView): # {{{
if ans is not None: if ans is not None:
db.prefs[name] = ans db.prefs[name] = ans
else: else:
injected = False
if not ans.get('last_modified_injected', False): if not ans.get('last_modified_injected', False):
injected = True
ans['last_modified_injected'] = True ans['last_modified_injected'] = True
hc = ans.get('hidden_columns', []) hc = ans.get('hidden_columns', [])
if 'last_modified' not in hc: if 'last_modified' not in hc:
hc.append('last_modified') hc.append('last_modified')
if not ans.get('languages_injected', False):
injected = True
ans['languages_injected'] = True
hc = ans.get('hidden_columns', [])
if 'languages' not in hc:
hc.append('languages')
if injected:
db.prefs[name] = ans db.prefs[name] = ans
return ans return ans
@ -501,7 +513,7 @@ class BooksView(QTableView): # {{{
for i in range(self.model().columnCount(None)): for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) in (self.rating_delegate, if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate, self.pubdate_delegate, self.timestamp_delegate, self.pubdate_delegate,
self.last_modified_delegate): self.last_modified_delegate, self.languages_delegate):
self.setItemDelegateForColumn(i, self.itemDelegate()) self.setItemDelegateForColumn(i, self.itemDelegate())
cm = self.column_map cm = self.column_map

View File

@ -34,6 +34,7 @@ from calibre.library.comments import comments_to_html
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.utils.icu import strcmp from calibre.utils.icu import strcmp
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.languages import LanguagesEdit as LE
def save_dialog(parent, title, msg, det_msg=''): def save_dialog(parent, title, msg, det_msg=''):
d = QMessageBox(parent) d = QMessageBox(parent)
@ -1133,6 +1134,43 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
# }}} # }}}
class LanguagesEdit(LE): # {{{
LABEL = _('&Languages:')
TOOLTIP = _('A comma separated list of languages for this book')
def __init__(self, *args, **kwargs):
LE.__init__(self, *args, **kwargs)
self.setToolTip(self.TOOLTIP)
@dynamic_property
def current_val(self):
def fget(self): return self.lang_codes
def fset(self, val): self.lang_codes = val
return property(fget=fget, fset=fset)
def initialize(self, db, id_):
lc = []
langs = db.languages(id_, index_is_id=True)
if langs:
lc = [x.strip() for x in langs.split(',')]
self.current_val = self.original_val = lc
def commit(self, db, id_):
bad = self.validate()
if bad:
error_dialog(self, _('Unknown language'),
ngettext('The language %s is not recognized',
'The languages %s are not recognized', len(bad))%(
', '.join(bad)),
show=True)
return False
cv = self.current_val
if cv != self.original_val:
db.set_languages(id_, cv)
return True
# }}}
class IdentifiersEdit(QLineEdit): # {{{ class IdentifiersEdit(QLineEdit): # {{{
LABEL = _('I&ds:') LABEL = _('I&ds:')
BASE_TT = _('Edit the identifiers for this book. ' BASE_TT = _('Edit the identifiers for this book. '

View File

@ -89,6 +89,15 @@ class ConfirmDialog(QDialog):
self.identify = False self.identify = False
self.accept() self.accept()
def split_jobs(ids, batch_size=100):
ans = []
ids = list(ids)
while ids:
jids = ids[:batch_size]
ans.append(jids)
ids = ids[batch_size:]
return ans
def start_download(gui, ids, callback): def start_download(gui, ids, callback):
d = ConfirmDialog(ids, gui) d = ConfirmDialog(ids, gui)
ret = d.exec_() ret = d.exec_()
@ -96,11 +105,13 @@ def start_download(gui, ids, callback):
if ret != d.Accepted: if ret != d.Accepted:
return return
job = ThreadedJob('metadata bulk download', for batch in split_jobs(ids):
_('Download metadata for %d books')%len(ids), job = ThreadedJob('metadata bulk download',
download, (ids, gui.current_db, d.identify, d.covers), {}, callback) _('Download metadata for %d books')%len(batch),
gui.job_manager.run_threaded_job(job) download, (batch, gui.current_db, d.identify, d.covers), {}, callback)
gui.job_manager.run_threaded_job(job)
gui.status_bar.show_message(_('Metadata download started'), 3000) gui.status_bar.show_message(_('Metadata download started'), 3000)
# }}} # }}}
def get_job_details(job): def get_job_details(job):

View File

@ -13,19 +13,21 @@ from functools import partial
from PyQt4.Qt import (Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, from PyQt4.Qt import (Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton,
QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont,
QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem,
QSizePolicy, QPalette, QFrame, QSize, QKeySequence, QMenu) QSizePolicy, QPalette, QFrame, QSize, QKeySequence, QMenu, QShortcut)
from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.ebooks.metadata import authors_to_string, string_to_authors
from calibre.gui2 import ResizableDialog, error_dialog, gprefs, pixmap_to_data from calibre.gui2 import ResizableDialog, error_dialog, gprefs, pixmap_to_data
from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit, from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit,
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, IdentifiersEdit, AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, IdentifiersEdit,
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit,
BuddyLabel, DateEdit, PubdateEdit) BuddyLabel, DateEdit, PubdateEdit, LanguagesEdit)
from calibre.gui2.metadata.single_download import FullFetch from calibre.gui2.metadata.single_download import FullFetch
from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
BASE_TITLE = _('Edit Metadata')
class MetadataSingleDialogBase(ResizableDialog): class MetadataSingleDialogBase(ResizableDialog):
view_format = pyqtSignal(object, object) view_format = pyqtSignal(object, object)
@ -43,6 +45,16 @@ class MetadataSingleDialogBase(ResizableDialog):
def setupUi(self, *args): # {{{ def setupUi(self, *args): # {{{
self.resize(990, 650) self.resize(990, 650)
self.download_shortcut = QShortcut(self)
self.download_shortcut.setKey(QKeySequence('Ctrl+D',
QKeySequence.PortableText))
p = self.parent()
if hasattr(p, 'keyboard'):
kname = u'Interface Action: Edit Metadata (Edit Metadata) : menu action : download'
sc = p.keyboard.keys_map.get(kname, None)
if sc:
self.download_shortcut.setKey(sc[0])
self.button_box = QDialogButtonBox( self.button_box = QDialogButtonBox(
QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal, QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal,
self) self)
@ -77,7 +89,7 @@ class MetadataSingleDialogBase(ResizableDialog):
ll.addSpacing(10) ll.addSpacing(10)
self.setWindowIcon(QIcon(I('edit_input.png'))) self.setWindowIcon(QIcon(I('edit_input.png')))
self.setWindowTitle(_('Edit Metadata')) self.setWindowTitle(BASE_TITLE)
self.create_basic_metadata_widgets() self.create_basic_metadata_widgets()
@ -183,6 +195,9 @@ class MetadataSingleDialogBase(ResizableDialog):
self.publisher = PublisherEdit(self) self.publisher = PublisherEdit(self)
self.basic_metadata_widgets.append(self.publisher) self.basic_metadata_widgets.append(self.publisher)
self.languages = LanguagesEdit(self)
self.basic_metadata_widgets.append(self.languages)
self.timestamp = DateEdit(self) self.timestamp = DateEdit(self)
self.pubdate = PubdateEdit(self) self.pubdate = PubdateEdit(self)
self.basic_metadata_widgets.extend([self.timestamp, self.pubdate]) self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])
@ -190,6 +205,7 @@ class MetadataSingleDialogBase(ResizableDialog):
self.fetch_metadata_button = QPushButton( self.fetch_metadata_button = QPushButton(
_('&Download metadata'), self) _('&Download metadata'), self)
self.fetch_metadata_button.clicked.connect(self.fetch_metadata) self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
self.download_shortcut.activated.connect(self.fetch_metadata_button.click)
font = self.fmb_font = QFont() font = self.fmb_font = QFont()
font.setBold(True) font.setBold(True)
self.fetch_metadata_button.setFont(font) self.fetch_metadata_button.setFont(font)
@ -264,8 +280,11 @@ class MetadataSingleDialogBase(ResizableDialog):
title = self.title.current_val title = self.title.current_val
if len(title) > 50: if len(title) > 50:
title = title[:50] + u'\u2026' title = title[:50] + u'\u2026'
self.setWindowTitle(_('Edit Metadata') + ' - ' + self.setWindowTitle(BASE_TITLE + ' - ' +
title) title + ' - ' +
_(' [%(num)d of %(tot)d]')%dict(num=
self.current_row+1,
tot=len(self.row_list)))
def swap_title_author(self, *args): def swap_title_author(self, *args):
title = self.title.current_val title = self.title.current_val
@ -351,6 +370,8 @@ class MetadataSingleDialogBase(ResizableDialog):
self.series.current_val = mi.series self.series.current_val = mi.series
if mi.series_index is not None: if mi.series_index is not None:
self.series_index.current_val = float(mi.series_index) self.series_index.current_val = float(mi.series_index)
if not mi.is_null('languages'):
self.languages.lang_codes = mi.languages
if mi.comments and mi.comments.strip(): if mi.comments and mi.comments.strip():
self.comments.current_val = mi.comments self.comments.current_val = mi.comments
@ -610,11 +631,13 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{
create_row2(5, self.pubdate, self.pubdate.clear_button) create_row2(5, self.pubdate, self.pubdate.clear_button)
sto(self.pubdate.clear_button, self.publisher) sto(self.pubdate.clear_button, self.publisher)
create_row2(6, self.publisher) create_row2(6, self.publisher)
sto(self.publisher, self.languages)
create_row2(7, self.languages)
self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding, self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding,
QSizePolicy.Expanding) QSizePolicy.Expanding)
l.addItem(self.tabs[0].spc_two, 8, 0, 1, 3) l.addItem(self.tabs[0].spc_two, 9, 0, 1, 3)
l.addWidget(self.fetch_metadata_button, 9, 0, 1, 2) l.addWidget(self.fetch_metadata_button, 10, 0, 1, 2)
l.addWidget(self.config_metadata_button, 9, 2, 1, 1) l.addWidget(self.config_metadata_button, 10, 2, 1, 1)
self.tabs[0].gb2 = gb = QGroupBox(_('Co&mments'), self) self.tabs[0].gb2 = gb = QGroupBox(_('Co&mments'), self)
gb.l = l = QVBoxLayout() gb.l = l = QVBoxLayout()
@ -717,16 +740,17 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
create_row(7, self.rating, self.pubdate) create_row(7, self.rating, self.pubdate)
create_row(8, self.pubdate, self.publisher, create_row(8, self.pubdate, self.publisher,
button=self.pubdate.clear_button, icon='trash.png') button=self.pubdate.clear_button, icon='trash.png')
create_row(9, self.publisher, self.timestamp) create_row(9, self.publisher, self.languages)
create_row(10, self.timestamp, self.identifiers, create_row(10, self.languages, self.timestamp)
create_row(11, self.timestamp, self.identifiers,
button=self.timestamp.clear_button, icon='trash.png') button=self.timestamp.clear_button, icon='trash.png')
create_row(11, self.identifiers, self.comments, create_row(12, self.identifiers, self.comments,
button=self.clear_identifiers_button, icon='trash.png') button=self.clear_identifiers_button, icon='trash.png')
sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.clear_identifiers_button, self.swap_title_author_button)
sto(self.swap_title_author_button, self.manage_authors_button) sto(self.swap_title_author_button, self.manage_authors_button)
sto(self.manage_authors_button, self.paste_isbn_button) sto(self.manage_authors_button, self.paste_isbn_button)
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding), tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
12, 1, 1 ,1) 13, 1, 1 ,1)
w = getattr(self, 'custom_metadata_widgets_parent', None) w = getattr(self, 'custom_metadata_widgets_parent', None)
if w is not None: if w is not None:
@ -852,16 +876,17 @@ class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{
create_row(7, self.rating, self.pubdate) create_row(7, self.rating, self.pubdate)
create_row(8, self.pubdate, self.publisher, create_row(8, self.pubdate, self.publisher,
button=self.pubdate.clear_button, icon='trash.png') button=self.pubdate.clear_button, icon='trash.png')
create_row(9, self.publisher, self.timestamp) create_row(9, self.publisher, self.languages)
create_row(10, self.timestamp, self.identifiers, create_row(10, self.languages, self.timestamp)
create_row(11, self.timestamp, self.identifiers,
button=self.timestamp.clear_button, icon='trash.png') button=self.timestamp.clear_button, icon='trash.png')
create_row(11, self.identifiers, self.comments, create_row(12, self.identifiers, self.comments,
button=self.clear_identifiers_button, icon='trash.png') button=self.clear_identifiers_button, icon='trash.png')
sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.clear_identifiers_button, self.swap_title_author_button)
sto(self.swap_title_author_button, self.manage_authors_button) sto(self.swap_title_author_button, self.manage_authors_button)
sto(self.manage_authors_button, self.paste_isbn_button) sto(self.manage_authors_button, self.paste_isbn_button)
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding), tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
12, 1, 1 ,1) 13, 1, 1 ,1)
# Custom metadata in col 1 # Custom metadata in col 1
w = getattr(self, 'custom_metadata_widgets_parent', None) w = getattr(self, 'custom_metadata_widgets_parent', None)

View File

@ -130,7 +130,7 @@ Author matching is exact.</string>
<item row="2" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_preserve_date_on_ctl"> <widget class="QCheckBox" name="opt_preserve_date_on_ctl">
<property name="text"> <property name="text">
<string>When &amp;copying books from one library to another, preserve the date</string> <string>When using the &quot;&amp;Copy to library&quot; action to copy books between libraries, preserve the date</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -161,7 +161,7 @@ class FieldsModel(QAbstractListModel): # {{{
'tags' : _('Tags'), 'tags' : _('Tags'),
'title': _('Title'), 'title': _('Title'),
'series': _('Series'), 'series': _('Series'),
'language': _('Language'), 'languages': _('Languages'),
} }
self.overrides = {} self.overrides = {}
self.exclude = frozenset(['series_index']) self.exclude = frozenset(['series_index'])

View File

@ -239,6 +239,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.plugin_view.selectionModel().select(idx, self.plugin_view.selectionModel().select(idx,
self.plugin_view.selectionModel().ClearAndSelect) self.plugin_view.selectionModel().ClearAndSelect)
self.plugin_view.setCurrentIndex(idx) self.plugin_view.setCurrentIndex(idx)
self.plugin_view.setFocus(Qt.OtherFocusReason)
def find_next(self, *args): def find_next(self, *args):
idx = self.plugin_view.currentIndex() idx = self.plugin_view.currentIndex()

View File

@ -108,6 +108,12 @@ class SearchBox2(QComboBox): # {{{
self.colorize = colorize self.colorize = colorize
self.clear() self.clear()
def hide_completer_popup(self):
try:
self.lineEdit().completer().popup().setVisible(False)
except:
pass
def normalize_state(self): def normalize_state(self):
self.setToolTip(self.tool_tip_text) self.setToolTip(self.tool_tip_text)
self.line_edit.setStyleSheet( self.line_edit.setStyleSheet(
@ -163,6 +169,8 @@ class SearchBox2(QComboBox): # {{{
# Comes from the combobox itself # Comes from the combobox itself
def keyPressEvent(self, event): def keyPressEvent(self, event):
k = event.key() k = event.key()
if k in (Qt.Key_Enter, Qt.Key_Return):
return self.do_search()
if k not in (Qt.Key_Up, Qt.Key_Down): if k not in (Qt.Key_Up, Qt.Key_Down):
QComboBox.keyPressEvent(self, event) QComboBox.keyPressEvent(self, event)
else: else:
@ -183,6 +191,7 @@ class SearchBox2(QComboBox): # {{{
self.do_search() self.do_search()
def _do_search(self, store_in_history=True): def _do_search(self, store_in_history=True):
self.hide_completer_popup()
text = unicode(self.currentText()).strip() text = unicode(self.currentText()).strip()
if not text: if not text:
return self.clear() return self.clear()
@ -219,15 +228,15 @@ class SearchBox2(QComboBox): # {{{
self.clear() self.clear()
else: else:
self.normalize_state() self.normalize_state()
self.lineEdit().setCompleter(None) # must turn on case sensitivity here so that tag browser strings
# are not case-insensitively replaced from history
self.line_edit.completer().setCaseSensitivity(Qt.CaseSensitive)
self.setEditText(txt) self.setEditText(txt)
self.line_edit.end(False) self.line_edit.end(False)
if emit_changed: if emit_changed:
self.changed.emit() self.changed.emit()
self._do_search(store_in_history=store_in_history) self._do_search(store_in_history=store_in_history)
c = QCompleter() self.line_edit.completer().setCaseSensitivity(Qt.CaseInsensitive)
self.lineEdit().setCompleter(c)
c.setCompletionMode(c.PopupCompletion)
self.focus_to_library.emit() self.focus_to_library.emit()
finally: finally:
if not store_in_history: if not store_in_history:

View File

@ -15,6 +15,7 @@ from calibre.utils.config import tweaks, prefs
from calibre.utils.date import parse_date, now, UNDEFINED_DATE from calibre.utils.date import parse_date, now, UNDEFINED_DATE
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.utils.localization import canonicalize_lang
from calibre.ebooks.metadata import title_sort, author_to_author_sort from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre import prints from calibre import prints
@ -721,9 +722,13 @@ class ResultCache(SearchQueryParser): # {{{
if loc == db_col['authors']: if loc == db_col['authors']:
### DB stores authors with commas changed to bars, so change query ### DB stores authors with commas changed to bars, so change query
if matchkind == REGEXP_MATCH: if matchkind == REGEXP_MATCH:
q = query.replace(',', r'\|'); q = query.replace(',', r'\|')
else: else:
q = query.replace(',', '|'); q = query.replace(',', '|')
elif loc == db_col['languages']:
q = canonicalize_lang(query)
if q is None:
q = query
else: else:
q = query q = query

View File

@ -39,6 +39,8 @@ from calibre.utils.magick.draw import save_cover_data_to
from calibre.utils.recycle_bin import delete_file, delete_tree from calibre.utils.recycle_bin import delete_file, delete_tree
from calibre.utils.formatter_functions import load_user_template_functions from calibre.utils.formatter_functions import load_user_template_functions
from calibre.db.errors import NoSuchFormat from calibre.db.errors import NoSuchFormat
from calibre.utils.localization import (canonicalize_lang,
calibre_langcode_to_name)
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
SPOOL_SIZE = 30*1024*1024 SPOOL_SIZE = 30*1024*1024
@ -372,6 +374,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'aum_sortconcat(link.id, authors.name, authors.sort, authors.link)'), 'aum_sortconcat(link.id, authors.name, authors.sort, authors.link)'),
'last_modified', 'last_modified',
'(SELECT identifiers_concat(type, val) FROM identifiers WHERE identifiers.book=books.id) identifiers', '(SELECT identifiers_concat(type, val) FROM identifiers WHERE identifiers.book=books.id) identifiers',
('languages', 'languages', 'lang_code',
'sortconcat(link.id, languages.lang_code)'),
] ]
lines = [] lines = []
for col in columns: for col in columns:
@ -390,7 +394,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'size':4, 'rating':5, 'tags':6, 'comments':7, 'series':8, 'size':4, 'rating':5, 'tags':6, 'comments':7, 'series':8,
'publisher':9, 'series_index':10, 'sort':11, 'author_sort':12, 'publisher':9, 'series_index':10, 'sort':11, 'author_sort':12,
'formats':13, 'path':14, 'pubdate':15, 'uuid':16, 'cover':17, 'formats':13, 'path':14, 'pubdate':15, 'uuid':16, 'cover':17,
'au_map':18, 'last_modified':19, 'identifiers':20} 'au_map':18, 'last_modified':19, 'identifiers':20, 'languages':21}
for k,v in self.FIELD_MAP.iteritems(): for k,v in self.FIELD_MAP.iteritems():
self.field_metadata.set_field_record_index(k, v, prefer_custom=False) self.field_metadata.set_field_record_index(k, v, prefer_custom=False)
@ -469,7 +473,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'author_sort', 'authors', 'comment', 'comments', 'author_sort', 'authors', 'comment', 'comments',
'publisher', 'rating', 'series', 'series_index', 'tags', 'publisher', 'rating', 'series', 'series_index', 'tags',
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice', 'title', 'timestamp', 'uuid', 'pubdate', 'ondevice',
'metadata_last_modified', 'metadata_last_modified', 'languages',
): ):
fm = {'comment':'comments', 'metadata_last_modified': fm = {'comment':'comments', 'metadata_last_modified':
'last_modified'}.get(prop, prop) 'last_modified'}.get(prop, prop)
@ -930,6 +934,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tags = row[fm['tags']] tags = row[fm['tags']]
if tags: if tags:
mi.tags = [i.strip() for i in tags.split(',')] mi.tags = [i.strip() for i in tags.split(',')]
languages = row[fm['languages']]
if languages:
mi.languages = [i.strip() for i in languages.split(',')]
mi.series = row[fm['series']] mi.series = row[fm['series']]
if mi.series: if mi.series:
mi.series_index = row[fm['series_index']] mi.series_index = row[fm['series_index']]
@ -1390,7 +1397,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
('authors', 'authors', 'author'), ('authors', 'authors', 'author'),
('publishers', 'publishers', 'publisher'), ('publishers', 'publishers', 'publisher'),
('tags', 'tags', 'tag'), ('tags', 'tags', 'tag'),
('series', 'series', 'series') ('series', 'series', 'series'),
('languages', 'languages', 'lang_code'),
]: ]:
doit(ltable, table, ltable_col) doit(ltable, table, ltable_col)
@ -1507,6 +1515,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'series' : self.get_series_with_ids, 'series' : self.get_series_with_ids,
'publisher': self.get_publishers_with_ids, 'publisher': self.get_publishers_with_ids,
'tags' : self.get_tags_with_ids, 'tags' : self.get_tags_with_ids,
'languages': self.get_languages_with_ids,
'rating' : self.get_ratings_with_ids, 'rating' : self.get_ratings_with_ids,
} }
func = funcs.get(category, None) func = funcs.get(category, None)
@ -1521,6 +1530,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for l in list: for l in list:
(id, val, sort_val) = (l[0], l[1], l[2]) (id, val, sort_val) = (l[0], l[1], l[2])
tids[category][val] = (id, sort_val) tids[category][val] = (id, sort_val)
elif category == 'languages':
for l in list:
id, val = l[0], calibre_langcode_to_name(l[1])
tids[category][l[1]] = (id, val)
elif cat['datatype'] == 'series': elif cat['datatype'] == 'series':
for l in list: for l in list:
(id, val) = (l[0], l[1]) (id, val) = (l[0], l[1])
@ -1620,6 +1633,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
item.rt += rating item.rt += rating
item.rc += 1 item.rc += 1
except: except:
prints(tid_cat, val)
prints('get_categories: item', val, 'is not in', cat, 'list!') prints('get_categories: item', val, 'is not in', cat, 'list!')
#print 'end phase "books":', time.clock() - last, 'seconds' #print 'end phase "books":', time.clock() - last, 'seconds'
@ -1684,6 +1698,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# Clean up the authors strings to human-readable form # Clean up the authors strings to human-readable form
formatter = (lambda x: x.replace('|', ',')) formatter = (lambda x: x.replace('|', ','))
items = [v for v in tcategories[category].values() if v.c > 0] items = [v for v in tcategories[category].values() if v.c > 0]
elif category == 'languages':
# Use a human readable language string
formatter = calibre_langcode_to_name
items = [v for v in tcategories[category].values() if v.c > 0]
else: else:
formatter = (lambda x:unicode(x)) formatter = (lambda x:unicode(x))
items = [v for v in tcategories[category].values() if v.c > 0] items = [v for v in tcategories[category].values() if v.c > 0]
@ -2043,6 +2061,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if should_replace_field('comments'): if should_replace_field('comments'):
doit(self.set_comment, id, mi.comments, notify=False, commit=False) doit(self.set_comment, id, mi.comments, notify=False, commit=False)
if should_replace_field('languages'):
doit(self.set_languages, id, mi.languages, notify=False, commit=False)
# Setting series_index to zero is acceptable # Setting series_index to zero is acceptable
if mi.series_index is not None: if mi.series_index is not None:
doit(self.set_series_index, id, mi.series_index, notify=False, doit(self.set_series_index, id, mi.series_index, notify=False,
@ -2265,6 +2286,37 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
def set_languages(self, book_id, languages, notify=True, commit=True):
self.conn.execute(
'DELETE FROM books_languages_link WHERE book=?', (book_id,))
self.conn.execute('''DELETE FROM languages WHERE (SELECT COUNT(id)
FROM books_languages_link WHERE
lang_code=languages.id) < 1''')
books_to_refresh = set([book_id])
final_languages = []
for l in languages:
lc = canonicalize_lang(l)
if not lc or lc in final_languages or lc in ('und', 'zxx', 'mis',
'mul'):
continue
final_languages.append(lc)
lc_id = self.conn.get('SELECT id FROM languages WHERE lang_code=?',
(lc,), all=False)
if lc_id is None:
lc_id = self.conn.execute('''INSERT INTO languages(lang_code)
VALUES (?)''', (lc,)).lastrowid
self.conn.execute('''INSERT INTO books_languages_link(book, lang_code)
VALUES (?,?)''', (book_id, lc_id))
self.dirtied(books_to_refresh, commit=False)
if commit:
self.conn.commit()
self.data.set(book_id, self.FIELD_MAP['languages'],
u','.join(final_languages), row_is_id=True)
if notify:
self.notify('metadata', [book_id])
return books_to_refresh
def set_timestamp(self, id, dt, notify=True, commit=True): def set_timestamp(self, id, dt, notify=True, commit=True):
if dt: if dt:
self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id)) self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id))
@ -2363,6 +2415,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return [] return []
return result return result
def get_languages_with_ids(self):
result = self.conn.get('SELECT id,lang_code FROM languages')
if not result:
return []
return result
def rename_tag(self, old_id, new_name): def rename_tag(self, old_id, new_name):
# It is possible that new_name is in fact a set of names. Split it on # It is possible that new_name is in fact a set of names. Split it on
# comma to find out. If it is, then rename the first one and append the # comma to find out. If it is, then rename the first one and append the

View File

@ -17,7 +17,7 @@ class TagsIcons(dict):
category_icons = ['authors', 'series', 'formats', 'publisher', 'rating', category_icons = ['authors', 'series', 'formats', 'publisher', 'rating',
'news', 'tags', 'custom:', 'user:', 'search', 'news', 'tags', 'custom:', 'user:', 'search',
'identifiers', 'gst'] 'identifiers', 'languages', 'gst']
def __init__(self, icon_dict): def __init__(self, icon_dict):
for a in self.category_icons: for a in self.category_icons:
if a not in icon_dict: if a not in icon_dict:
@ -37,6 +37,7 @@ category_icon_map = {
'search' : 'search.png', 'search' : 'search.png',
'identifiers': 'identifiers.png', 'identifiers': 'identifiers.png',
'gst' : 'catalog.png', 'gst' : 'catalog.png',
'languages' : 'languages.png',
} }
@ -114,6 +115,21 @@ class FieldMetadata(dict):
'is_custom':False, 'is_custom':False,
'is_category':True, 'is_category':True,
'is_csp': False}), 'is_csp': False}),
('languages', {'table':'languages',
'column':'lang_code',
'link_column':'lang_code',
'category_sort':'lang_code',
'datatype':'text',
'is_multiple':{'cache_to_list': ',',
'ui_to_list': ',',
'list_to_ui': ', '},
'kind':'field',
'name':_('Languages'),
'search_terms':['languages', 'language'],
'is_custom':False,
'is_category':True,
'is_csp': False}),
('series', {'table':'series', ('series', {'table':'series',
'column':'name', 'column':'name',
'link_column':'series', 'link_column':'series',

View File

@ -111,12 +111,12 @@ def config(defaults=None):
'to supports unicode.')) 'to supports unicode.'))
x('timefmt', default='%b, %Y', x('timefmt', default='%b, %Y',
help=_('The format in which to display dates. %(day)s - day,' help=_('The format in which to display dates. %(day)s - day,'
' %(month)s - month, %(year)s - year. Default is: %(default)s' ' %(month)s - month, %(mn)s - month number, %(year)s - year. Default is: %(default)s'
)%dict(day='%d', month='%b', year='%Y', default='%b, %Y')) )%dict(day='%d', month='%b', mn='%m', year='%Y', default='%b, %Y'))
x('send_timefmt', default='%b, %Y', x('send_timefmt', default='%b, %Y',
help=_('The format in which to display dates. %(day)s - day,' help=_('The format in which to display dates. %(day)s - day,'
' %(month)s - month, %(year)s - year. Default is: %(default)s' ' %(month)s - month, %(mn)s - month number, %(year)s - year. Default is: %(default)s'
)%dict(day='%d', month='%b', year='%Y', default='%b, %Y')) )%dict(day='%d', month='%b', mn='%m', year='%Y', default='%b, %Y'))
x('to_lowercase', default=False, x('to_lowercase', default=False,
help=_('Convert paths to lowercase.')) help=_('Convert paths to lowercase.'))
x('replace_whitespace', default=False, x('replace_whitespace', default=False,

View File

@ -565,7 +565,9 @@ Convert Microsoft Word documents
|app| does not directly convert .doc/.docx files from Microsoft Word. However, in Word, you can save the document |app| does not directly convert .doc/.docx files from Microsoft Word. However, in Word, you can save the document
as HTML and then convert the resulting HTML file with |app|. When saving as HTML, be sure to use the as HTML and then convert the resulting HTML file with |app|. When saving as HTML, be sure to use the
"Save as Web Page, Filtered" option as this will produce clean HTML that will convert well. Note that Word "Save as Web Page, Filtered" option as this will produce clean HTML that will convert well. Note that Word
produces really messy HTML, converting it can take a long time, so be patient. produces really messy HTML, converting it can take a long time, so be patient. Another alternative is to
use the free OpenOffice. Open your .doc file in OpenOffice and save it in OpenOffice's format .odt. |app| can
directly convert .odt files.
There is a Word macro package that can automate the conversion of Word documents using |app|. It also makes There is a Word macro package that can automate the conversion of Word documents using |app|. It also makes
generating the Table of Contents much simpler. It is called BookCreator and is available for free generating the Table of Contents much simpler. It is called BookCreator and is available for free

View File

@ -555,7 +555,7 @@ If you still cannot get the installer to work and you are on windows, you can us
My antivirus program claims |app| is a virus/trojan? My antivirus program claims |app| is a virus/trojan?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Your antivirus program is wrong. |app| is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you) to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. If the antivirus program is preventing you from downloading/installing |app|, disable it temporarily, install |app| and then re-enable it. Your antivirus program is wrong. Antivirus programs use heuristics, patterns of code that "looks suspicuous" to detect viruses. It's rather like racial profiling. |app| is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you) to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. If the antivirus program is preventing you from downloading/installing |app|, disable it temporarily, install |app| and then re-enable it.
How do I backup |app|? How do I backup |app|?
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -6,8 +6,9 @@ meaning as possible.
import os import os
from math import ceil from math import ceil
from calibre import sanitize_file_name from calibre import sanitize_file_name, isbytestring, force_unicode
from calibre.constants import preferred_encoding, iswindows from calibre.constants import (preferred_encoding, iswindows,
filesystem_encoding)
from calibre.utils.localization import get_udc from calibre.utils.localization import get_udc
def ascii_text(orig): def ascii_text(orig):
@ -114,3 +115,83 @@ def is_case_sensitive(path):
os.remove(f1) os.remove(f1)
return is_case_sensitive return is_case_sensitive
def case_preserving_open_file(path, mode='wb', mkdir_mode=0777):
'''
Open the file pointed to by path with the specified mode. If any
directories in path do not exist, they are created. Returns the
opened file object and the path to the opened file object. This path is
guaranteed to have the same case as the on disk path. For case insensitive
filesystems, the returned path may be different from the passed in path.
The returned path is always unicode and always an absolute path.
If mode is None, then this function assumes that path points to a directory
and return the path to the directory as the file object.
mkdir_mode specifies the mode with which any missing directories in path
are created.
'''
if isbytestring(path):
path = path.decode(filesystem_encoding)
path = os.path.abspath(path)
sep = force_unicode(os.sep, 'ascii')
if path.endswith(sep):
path = path[:-1]
if not path:
raise ValueError('Path must not point to root')
components = path.split(sep)
if not components:
raise ValueError('Invalid path: %r'%path)
cpath = sep
if iswindows:
# Always upper case the drive letter and add a trailing slash so that
# the first os.listdir works correctly
cpath = components[0].upper() + sep
bdir = path if mode is None else os.path.dirname(path)
if not os.path.exists(bdir):
os.makedirs(bdir, mkdir_mode)
# Walk all the directories in path, putting the on disk case version of
# the directory into cpath
dirs = components[1:] if mode is None else components[1:-1]
for comp in dirs:
cdir = os.path.join(cpath, comp)
cl = comp.lower()
try:
candidates = [c for c in os.listdir(cpath) if c.lower() == cl]
except:
# Dont have permission to do the listdir, assume the case is
# correct as we have no way to check it.
pass
else:
if len(candidates) == 1:
cdir = os.path.join(cpath, candidates[0])
# else: We are on a case sensitive file system so cdir must already
# be correct
cpath = cdir
if mode is None:
ans = fpath = cpath
else:
fname = components[-1]
ans = open(os.path.join(cpath, fname), mode)
# Ensure file and all its metadata is written to disk so that subsequent
# listdir() has file name in it. I don't know if this is actually
# necessary, but given the diversity of platforms, best to be safe.
ans.flush()
os.fsync(ans.fileno())
cl = fname.lower()
candidates = [c for c in os.listdir(cpath) if c.lower() == cl]
if len(candidates) == 1:
fpath = os.path.join(cpath, candidates[0])
else:
# We are on a case sensitive filesystem
fpath = os.path.join(cpath, fname)
return ans, fpath

View File

@ -963,7 +963,7 @@ class BuiltinListSort(BuiltinFormatterFunction):
def evaluate(self, formatter, kwargs, mi, locals, list1, direction, separator): def evaluate(self, formatter, kwargs, mi, locals, list1, direction, separator):
res = [l.strip() for l in list1.split(separator) if l.strip()] res = [l.strip() for l in list1.split(separator) if l.strip()]
return ', '.join(sorted(res, key=sort_key, reverse=direction != 0)) return ', '.join(sorted(res, key=sort_key, reverse=direction != "0"))
class BuiltinToday(BuiltinFormatterFunction): class BuiltinToday(BuiltinFormatterFunction):
name = 'today' name = 'today'

View File

@ -192,6 +192,80 @@ def get_language(lang):
ans = iso639['by_3t'].get(lang, ans) ans = iso639['by_3t'].get(lang, ans)
return translate(ans) return translate(ans)
def calibre_langcode_to_name(lc, localize=True):
iso639 = _load_iso639()
translate = _ if localize else lambda x: x
try:
return translate(iso639['by_3t'][lc])
except:
pass
return lc
def canonicalize_lang(raw):
if not raw:
return None
if not isinstance(raw, unicode):
raw = raw.decode('utf-8', 'ignore')
raw = raw.lower().strip()
if not raw:
return None
raw = raw.replace('_', '-').partition('-')[0].strip()
if not raw:
return None
iso639 = _load_iso639()
m2to3 = iso639['2to3']
if len(raw) == 2:
ans = m2to3.get(raw, None)
if ans is not None:
return ans
elif len(raw) == 3:
if raw in iso639['by_3t']:
return raw
if raw in iso639['3bto3t']:
return iso639['3bto3t'][raw]
return iso639['name_map'].get(raw, None)
_lang_map = None
def lang_map():
' Return mapping of ISO 639 3 letter codes to localized language names '
iso639 = _load_iso639()
translate = _
global _lang_map
if _lang_map is None:
_lang_map = {k:translate(v) for k, v in iso639['by_3t'].iteritems()}
return _lang_map
def langnames_to_langcodes(names):
'''
Given a list of localized language names return a mapping of the names to 3
letter ISO 639 language codes. If a name is not recognized, it is mapped to
None.
'''
iso639 = _load_iso639()
translate = _
ans = {}
names = set(names)
for k, v in iso639['by_3t'].iteritems():
tv = translate(v)
if tv in names:
names.remove(tv)
ans[tv] = k
if not names:
break
for x in names:
ans[x] = None
return ans
def lang_as_iso639_1(name_or_code):
code = canonicalize_lang(name_or_code)
if code is not None:
iso639 = _load_iso639()
return iso639['3to2'].get(code, None)
_udc = None _udc = None
def get_udc(): def get_udc():